From 4b5d2cd0ddcca7dd520847605bef1d2d0586b5f1 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 9 Mar 2020 09:00:03 +0100 Subject: [PATCH 1/7] 2177 account module LDAP test RC1 SQL version changed Tests fixes Back & front tests fixed All tests passed Front and back tests plus some fixes --- back/methods/account/change-password.js | 64 +++ back/methods/account/set-password.js | 33 ++ .../account/specs/change-password.spec.js | 9 + .../account/specs/set-password.spec.js | 15 + back/model-config.json | 3 + back/models/account.js | 2 + back/models/account.json | 3 + back/models/language.json | 34 ++ db/changes/10211-accountModule/00-account.sql | 47 ++ .../00-myUserChangePassword.sql | 13 + .../00-myUserCheckLogin.sql | 15 + .../10211-accountModule/00-myUserGetId.sql | 15 + .../10211-accountModule/00-myUserGetName.sql | 15 + .../10211-accountModule/00-myUserHasRole.sql | 14 + .../00-myUserHasRoleId.sql | 14 + .../00-myUser_changePassword.sql | 17 + .../00-myUser_checkLogin.sql | 29 + .../10211-accountModule/00-myUser_getId.sql | 27 + .../10211-accountModule/00-myUser_getName.sql | 27 + .../10211-accountModule/00-myUser_hasRole.sql | 17 + .../00-myUser_hasRoleId.sql | 17 + .../10211-accountModule/00-myUser_login.sql | 29 + .../00-myUser_loginWithKey.sql | 25 + .../00-myUser_loginWithName.sql | 26 + .../10211-accountModule/00-myUser_logout.sql | 15 + .../00-passwordGenerate.sql | 52 ++ .../10211-accountModule/00-role_checkName.sql | 18 + .../00-role_getDescendents.sql | 66 +++ .../10211-accountModule/00-role_sync.sql | 53 ++ .../00-role_syncPrivileges.sql | 494 ++++++++++++++++++ .../10211-accountModule/00-userGetId.sql | 15 + .../00-userGetMysqlRole.sql | 11 + .../10211-accountModule/00-userGetName.sql | 15 + .../00-userGetNameFromId.sql | 11 + .../10211-accountModule/00-userHasRole.sql | 12 + .../10211-accountModule/00-userHasRoleId.sql | 12 + .../10211-accountModule/00-userLogin.sql | 14 + .../00-userLoginWithKey.sql | 14 + .../00-userLoginWithName.sql | 12 + .../10211-accountModule/00-userLogout.sql | 14 + .../00-userSetPassword.sql | 16 + .../00-user_changePassword.sql | 27 + .../10211-accountModule/00-user_checkName.sql | 17 + .../00-user_getMysqlRole.sql | 22 + .../00-user_getNameFromId.sql | 20 + .../10211-accountModule/00-user_hasRole.sql | 25 + .../10211-accountModule/00-user_hasRoleId.sql | 25 + .../00-user_setPassword.sql | 23 + db/dump/fixtures.sql | 46 +- .../core/components/crud-model/crud-model.js | 2 +- front/core/components/data-viewer/index.html | 1 + front/core/components/list/style.scss | 5 +- front/core/components/menu/menu.js | 1 + front/core/components/menu/style.scss | 7 + .../components/model-proxy/model-proxy.js | 23 +- front/core/components/searchbar/locale/en.yml | 2 +- front/core/components/searchbar/locale/es.yml | 2 +- front/core/components/searchbar/searchbar.js | 23 +- front/core/components/tooltip/tooltip.js | 5 +- front/core/components/watcher/watcher.js | 382 +++++++++----- front/core/components/watcher/watcher.spec.js | 188 +++---- front/core/directives/rule.js | 2 +- front/core/styles/effects.scss | 6 + .../styles/icons/MaterialIcons-Regular.woff2 | Bin 44300 -> 82512 bytes front/module-import.js | 1 + front/salix/components/descriptor/index.html | 4 +- front/salix/components/descriptor/index.js | 11 + .../salix/components/left-menu/left-menu.html | 4 +- front/salix/components/left-menu/left-menu.js | 119 +++-- front/salix/locale/es.yml | 1 + loopback/locale/es.json | 9 +- modules/account/back/model-config.json | 24 + .../back/models/mail-alias-account.json | 27 + modules/account/back/models/mail-alias.json | 33 ++ modules/account/back/models/mail-forward.json | 25 + modules/account/back/models/role-inherit.json | 27 + modules/account/back/models/role-role.js | 20 + modules/account/back/models/role-role.json | 26 + modules/account/back/models/user-account.json | 26 + .../account/back/models/user-password.json | 34 ++ modules/account/front/acl/create/index.html | 65 +++ modules/account/front/acl/create/index.js | 33 ++ modules/account/front/acl/index.js | 4 + modules/account/front/acl/index/index.html | 51 ++ modules/account/front/acl/index/index.js | 15 + modules/account/front/acl/index/locale/es.yml | 4 + modules/account/front/acl/locale/es.yml | 4 + modules/account/front/acl/main/index.html | 20 + modules/account/front/acl/main/index.js | 18 + .../account/front/acl/search-panel/index.html | 39 ++ .../account/front/acl/search-panel/index.js | 26 + .../account/front/alias/basic-data/index.html | 44 ++ .../account/front/alias/basic-data/index.js | 12 + modules/account/front/alias/card/index.html | 5 + modules/account/front/alias/card/index.js | 14 + modules/account/front/alias/create/index.html | 33 ++ modules/account/front/alias/create/index.js | 15 + .../account/front/alias/descriptor/index.html | 27 + .../account/front/alias/descriptor/index.js | 26 + .../front/alias/descriptor/locale/es.yml | 2 + modules/account/front/alias/index.js | 9 + modules/account/front/alias/index/index.html | 39 ++ modules/account/front/alias/index/index.js | 14 + .../account/front/alias/index/locale/es.yml | 2 + modules/account/front/alias/locale/es.yml | 1 + modules/account/front/alias/main/index.html | 17 + modules/account/front/alias/main/index.js | 18 + .../account/front/alias/summary/index.html | 16 + modules/account/front/alias/summary/index.js | 25 + modules/account/front/alias/users/index.html | 26 + modules/account/front/alias/users/index.js | 31 ++ .../account/front/alias/users/index.spec.js | 42 ++ .../account/front/alias/users/locale/es.yml | 2 + modules/account/front/aliases/index.html | 64 +++ modules/account/front/aliases/index.js | 51 ++ modules/account/front/aliases/index.spec.js | 53 ++ modules/account/front/aliases/locale/es.yml | 3 + modules/account/front/basic-data/index.html | 52 ++ modules/account/front/basic-data/index.js | 20 + modules/account/front/card/index.html | 8 + modules/account/front/card/index.js | 28 + modules/account/front/card/index.spec.js | 27 + modules/account/front/card/style.scss | 10 + modules/account/front/connections/index.html | 45 ++ modules/account/front/connections/index.js | 29 + .../account/front/connections/locale/es.yml | 5 + modules/account/front/create/index.html | 53 ++ modules/account/front/create/index.js | 15 + .../__snapshots__/index.spec.js.snap | 5 + modules/account/front/descriptor/index.html | 164 ++++++ modules/account/front/descriptor/index.js | 123 +++++ .../account/front/descriptor/index.spec.js | 108 ++++ .../account/front/descriptor/locale/es.yml | 31 ++ modules/account/front/index.js | 17 + modules/account/front/index/index.html | 44 ++ modules/account/front/index/index.js | 14 + modules/account/front/index/locale/es.yml | 2 + modules/account/front/locale/es.yml | 10 + .../account/front/mail-forwarding/index.html | 43 ++ .../account/front/mail-forwarding/index.js | 12 + .../front/mail-forwarding/locale/es.yml | 6 + modules/account/front/main/index.html | 18 + modules/account/front/main/index.js | 39 ++ modules/account/front/main/index.spec.js | 28 + modules/account/front/module.js | 3 + .../account/front/role/basic-data/index.html | 39 ++ .../account/front/role/basic-data/index.js | 12 + modules/account/front/role/card/index.html | 5 + modules/account/front/role/card/index.js | 14 + modules/account/front/role/card/index.spec.js | 25 + modules/account/front/role/create/index.html | 33 ++ modules/account/front/role/create/index.js | 15 + .../account/front/role/descriptor/index.html | 27 + .../account/front/role/descriptor/index.js | 26 + .../front/role/descriptor/index.spec.js | 29 + .../front/role/descriptor/locale/es.yml | 2 + modules/account/front/role/index.js | 10 + modules/account/front/role/index/index.html | 42 ++ modules/account/front/role/index/index.js | 14 + .../account/front/role/index/locale/es.yml | 2 + .../account/front/role/inherited/index.html | 21 + modules/account/front/role/inherited/index.js | 23 + .../front/role/inherited/index.spec.js | 23 + modules/account/front/role/locale/es.yml | 1 + modules/account/front/role/main/index.html | 18 + modules/account/front/role/main/index.js | 24 + .../front/role/search-panel/index.html | 21 + .../account/front/role/search-panel/index.js | 7 + .../account/front/role/subroles/index.html | 57 ++ modules/account/front/role/subroles/index.js | 51 ++ .../account/front/role/subroles/index.spec.js | 53 ++ .../account/front/role/subroles/locale/es.yml | 4 + modules/account/front/role/summary/index.html | 20 + modules/account/front/role/summary/index.js | 25 + modules/account/front/roles/index.html | 21 + modules/account/front/roles/index.js | 26 + modules/account/front/routes.json | 199 +++++++ modules/account/front/search-panel/index.html | 31 ++ modules/account/front/search-panel/index.js | 7 + modules/account/front/summary/index.html | 20 + modules/account/front/summary/index.js | 33 ++ modules/claim/front/basic-data/index.html | 17 +- .../specs/activeWorkersWithRole.spec.js | 4 +- .../methods/client/specs/listWorkers.spec.js | 2 +- .../client/front/address/create/index.html | 2 +- modules/client/front/create/index.html | 36 +- .../insurance/create/index.html | 9 +- modules/client/front/descriptor/index.html | 5 + modules/client/front/greuge/create/index.html | 10 +- modules/client/front/greuge/create/index.js | 18 +- modules/client/front/note/create/index.html | 2 +- .../client/front/recovery/create/index.html | 14 +- modules/client/front/sample/create/index.html | 26 +- modules/entry/front/create/index.html | 21 +- modules/item/front/botanical/index.html | 33 +- modules/item/front/botanical/index.js | 25 +- modules/item/front/botanical/index.spec.js | 28 - modules/item/front/create/index.html | 27 +- modules/item/front/tax/index.spec.js | 13 +- modules/route/front/create/index.html | 31 +- .../ticket/front/request/create/index.html | 23 +- modules/travel/front/create/index.html | 30 +- modules/worker/front/descriptor/index.html | 8 +- modules/zone/front/create/index.html | 25 +- package-lock.json | 151 +++++- package.json | 1 + print/core/config.js | 4 +- print/core/filters/specs/number.spec.js | 3 +- print/core/filters/specs/percentage.spec.js | 3 +- 209 files changed, 5421 insertions(+), 558 deletions(-) create mode 100644 back/methods/account/change-password.js create mode 100644 back/methods/account/set-password.js create mode 100644 back/methods/account/specs/change-password.spec.js create mode 100644 back/methods/account/specs/set-password.spec.js create mode 100644 back/models/language.json create mode 100644 db/changes/10211-accountModule/00-account.sql create mode 100644 db/changes/10211-accountModule/00-myUserChangePassword.sql create mode 100644 db/changes/10211-accountModule/00-myUserCheckLogin.sql create mode 100644 db/changes/10211-accountModule/00-myUserGetId.sql create mode 100644 db/changes/10211-accountModule/00-myUserGetName.sql create mode 100644 db/changes/10211-accountModule/00-myUserHasRole.sql create mode 100644 db/changes/10211-accountModule/00-myUserHasRoleId.sql create mode 100644 db/changes/10211-accountModule/00-myUser_changePassword.sql create mode 100644 db/changes/10211-accountModule/00-myUser_checkLogin.sql create mode 100644 db/changes/10211-accountModule/00-myUser_getId.sql create mode 100644 db/changes/10211-accountModule/00-myUser_getName.sql create mode 100644 db/changes/10211-accountModule/00-myUser_hasRole.sql create mode 100644 db/changes/10211-accountModule/00-myUser_hasRoleId.sql create mode 100644 db/changes/10211-accountModule/00-myUser_login.sql create mode 100644 db/changes/10211-accountModule/00-myUser_loginWithKey.sql create mode 100644 db/changes/10211-accountModule/00-myUser_loginWithName.sql create mode 100644 db/changes/10211-accountModule/00-myUser_logout.sql create mode 100644 db/changes/10211-accountModule/00-passwordGenerate.sql create mode 100644 db/changes/10211-accountModule/00-role_checkName.sql create mode 100644 db/changes/10211-accountModule/00-role_getDescendents.sql create mode 100644 db/changes/10211-accountModule/00-role_sync.sql create mode 100644 db/changes/10211-accountModule/00-role_syncPrivileges.sql create mode 100644 db/changes/10211-accountModule/00-userGetId.sql create mode 100644 db/changes/10211-accountModule/00-userGetMysqlRole.sql create mode 100644 db/changes/10211-accountModule/00-userGetName.sql create mode 100644 db/changes/10211-accountModule/00-userGetNameFromId.sql create mode 100644 db/changes/10211-accountModule/00-userHasRole.sql create mode 100644 db/changes/10211-accountModule/00-userHasRoleId.sql create mode 100644 db/changes/10211-accountModule/00-userLogin.sql create mode 100644 db/changes/10211-accountModule/00-userLoginWithKey.sql create mode 100644 db/changes/10211-accountModule/00-userLoginWithName.sql create mode 100644 db/changes/10211-accountModule/00-userLogout.sql create mode 100644 db/changes/10211-accountModule/00-userSetPassword.sql create mode 100644 db/changes/10211-accountModule/00-user_changePassword.sql create mode 100644 db/changes/10211-accountModule/00-user_checkName.sql create mode 100644 db/changes/10211-accountModule/00-user_getMysqlRole.sql create mode 100644 db/changes/10211-accountModule/00-user_getNameFromId.sql create mode 100644 db/changes/10211-accountModule/00-user_hasRole.sql create mode 100644 db/changes/10211-accountModule/00-user_hasRoleId.sql create mode 100644 db/changes/10211-accountModule/00-user_setPassword.sql create mode 100644 front/core/components/menu/style.scss create mode 100644 modules/account/back/model-config.json create mode 100644 modules/account/back/models/mail-alias-account.json create mode 100644 modules/account/back/models/mail-alias.json create mode 100644 modules/account/back/models/mail-forward.json create mode 100644 modules/account/back/models/role-inherit.json create mode 100644 modules/account/back/models/role-role.js create mode 100644 modules/account/back/models/role-role.json create mode 100644 modules/account/back/models/user-account.json create mode 100644 modules/account/back/models/user-password.json create mode 100644 modules/account/front/acl/create/index.html create mode 100644 modules/account/front/acl/create/index.js create mode 100644 modules/account/front/acl/index.js create mode 100644 modules/account/front/acl/index/index.html create mode 100644 modules/account/front/acl/index/index.js create mode 100644 modules/account/front/acl/index/locale/es.yml create mode 100644 modules/account/front/acl/locale/es.yml create mode 100644 modules/account/front/acl/main/index.html create mode 100644 modules/account/front/acl/main/index.js create mode 100644 modules/account/front/acl/search-panel/index.html create mode 100644 modules/account/front/acl/search-panel/index.js create mode 100644 modules/account/front/alias/basic-data/index.html create mode 100644 modules/account/front/alias/basic-data/index.js create mode 100644 modules/account/front/alias/card/index.html create mode 100644 modules/account/front/alias/card/index.js create mode 100644 modules/account/front/alias/create/index.html create mode 100644 modules/account/front/alias/create/index.js create mode 100644 modules/account/front/alias/descriptor/index.html create mode 100644 modules/account/front/alias/descriptor/index.js create mode 100644 modules/account/front/alias/descriptor/locale/es.yml create mode 100644 modules/account/front/alias/index.js create mode 100644 modules/account/front/alias/index/index.html create mode 100644 modules/account/front/alias/index/index.js create mode 100644 modules/account/front/alias/index/locale/es.yml create mode 100644 modules/account/front/alias/locale/es.yml create mode 100644 modules/account/front/alias/main/index.html create mode 100644 modules/account/front/alias/main/index.js create mode 100644 modules/account/front/alias/summary/index.html create mode 100644 modules/account/front/alias/summary/index.js create mode 100644 modules/account/front/alias/users/index.html create mode 100644 modules/account/front/alias/users/index.js create mode 100644 modules/account/front/alias/users/index.spec.js create mode 100644 modules/account/front/alias/users/locale/es.yml create mode 100644 modules/account/front/aliases/index.html create mode 100644 modules/account/front/aliases/index.js create mode 100644 modules/account/front/aliases/index.spec.js create mode 100644 modules/account/front/aliases/locale/es.yml create mode 100644 modules/account/front/basic-data/index.html create mode 100644 modules/account/front/basic-data/index.js create mode 100644 modules/account/front/card/index.html create mode 100644 modules/account/front/card/index.js create mode 100644 modules/account/front/card/index.spec.js create mode 100644 modules/account/front/card/style.scss create mode 100644 modules/account/front/connections/index.html create mode 100644 modules/account/front/connections/index.js create mode 100644 modules/account/front/connections/locale/es.yml create mode 100644 modules/account/front/create/index.html create mode 100644 modules/account/front/create/index.js create mode 100644 modules/account/front/descriptor/__snapshots__/index.spec.js.snap create mode 100644 modules/account/front/descriptor/index.html create mode 100644 modules/account/front/descriptor/index.js create mode 100644 modules/account/front/descriptor/index.spec.js create mode 100644 modules/account/front/descriptor/locale/es.yml create mode 100644 modules/account/front/index.js create mode 100644 modules/account/front/index/index.html create mode 100644 modules/account/front/index/index.js create mode 100644 modules/account/front/index/locale/es.yml create mode 100644 modules/account/front/locale/es.yml create mode 100644 modules/account/front/mail-forwarding/index.html create mode 100644 modules/account/front/mail-forwarding/index.js create mode 100644 modules/account/front/mail-forwarding/locale/es.yml create mode 100644 modules/account/front/main/index.html create mode 100644 modules/account/front/main/index.js create mode 100644 modules/account/front/main/index.spec.js create mode 100644 modules/account/front/module.js create mode 100644 modules/account/front/role/basic-data/index.html create mode 100644 modules/account/front/role/basic-data/index.js create mode 100644 modules/account/front/role/card/index.html create mode 100644 modules/account/front/role/card/index.js create mode 100644 modules/account/front/role/card/index.spec.js create mode 100644 modules/account/front/role/create/index.html create mode 100644 modules/account/front/role/create/index.js create mode 100644 modules/account/front/role/descriptor/index.html create mode 100644 modules/account/front/role/descriptor/index.js create mode 100644 modules/account/front/role/descriptor/index.spec.js create mode 100644 modules/account/front/role/descriptor/locale/es.yml create mode 100644 modules/account/front/role/index.js create mode 100644 modules/account/front/role/index/index.html create mode 100644 modules/account/front/role/index/index.js create mode 100644 modules/account/front/role/index/locale/es.yml create mode 100644 modules/account/front/role/inherited/index.html create mode 100644 modules/account/front/role/inherited/index.js create mode 100644 modules/account/front/role/inherited/index.spec.js create mode 100644 modules/account/front/role/locale/es.yml create mode 100644 modules/account/front/role/main/index.html create mode 100644 modules/account/front/role/main/index.js create mode 100644 modules/account/front/role/search-panel/index.html create mode 100644 modules/account/front/role/search-panel/index.js create mode 100644 modules/account/front/role/subroles/index.html create mode 100644 modules/account/front/role/subroles/index.js create mode 100644 modules/account/front/role/subroles/index.spec.js create mode 100644 modules/account/front/role/subroles/locale/es.yml create mode 100644 modules/account/front/role/summary/index.html create mode 100644 modules/account/front/role/summary/index.js create mode 100644 modules/account/front/roles/index.html create mode 100644 modules/account/front/roles/index.js create mode 100644 modules/account/front/routes.json create mode 100644 modules/account/front/search-panel/index.html create mode 100644 modules/account/front/search-panel/index.js create mode 100644 modules/account/front/summary/index.html create mode 100644 modules/account/front/summary/index.js delete mode 100644 modules/item/front/botanical/index.spec.js diff --git a/back/methods/account/change-password.js b/back/methods/account/change-password.js new file mode 100644 index 000000000..b2afd5402 --- /dev/null +++ b/back/methods/account/change-password.js @@ -0,0 +1,64 @@ + +module.exports = Self => { + Self.remoteMethod('changePassword', { + description: 'Changes the user password', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, { + arg: 'id', + type: 'Number', + description: 'The user id', + http: {source: 'path'} + }, { + arg: 'oldPassword', + type: 'String', + description: 'The old password', + required: true + }, { + arg: 'newPassword', + type: 'String', + description: 'The new password', + required: true + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/:id/changePassword`, + verb: 'PATCH' + } + }); + + Self.changePassword = async function(ctx, id, oldPassword, newPassword) { + let params = [id, oldPassword, newPassword]; + await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`, params); + + /* + const ldap = require('ldapjs'); + + let ldapConf = { + url: 'ldap://domain.local:389', + dn: 'cn=admin,dc=domain,dc=local', + password: '123456' + }; + + await new Promise((reject, resolve) => { + let client = ldap.createClient({url: ldapConf.url}); + + client.bind(ldapConf.dn, ldapConf.password, err => { + if (err) + reject(err); + else + resolve(); + }); + }); + */ + + return true; + }; +}; diff --git a/back/methods/account/set-password.js b/back/methods/account/set-password.js new file mode 100644 index 000000000..027649548 --- /dev/null +++ b/back/methods/account/set-password.js @@ -0,0 +1,33 @@ + +module.exports = Self => { + Self.remoteMethod('setPassword', { + description: 'Sets the user password', + accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The user id', + http: {source: 'path'} + }, { + arg: 'newPassword', + type: 'String', + description: 'The new password', + required: true + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/:id/setPassword`, + verb: 'PATCH' + } + }); + + Self.setPassword = async function(id, newPassword) { + let params = [id, newPassword]; + await Self.rawSql(`CALL account.user_setPassword(?, ?)`, params); + return true; + }; +}; diff --git a/back/methods/account/specs/change-password.spec.js b/back/methods/account/specs/change-password.spec.js new file mode 100644 index 000000000..9f1130df5 --- /dev/null +++ b/back/methods/account/specs/change-password.spec.js @@ -0,0 +1,9 @@ +const app = require('vn-loopback/server/server'); + +describe('account changePassword()', () => { + it('should throw an error when old password is wrong', async() => { + let req = app.models.Account.changePassword(null, 1, 'wrongOldPass', 'newPass'); + + await expectAsync(req).toBeRejected(); + }); +}); diff --git a/back/methods/account/specs/set-password.spec.js b/back/methods/account/specs/set-password.spec.js new file mode 100644 index 000000000..c76fd52b8 --- /dev/null +++ b/back/methods/account/specs/set-password.spec.js @@ -0,0 +1,15 @@ +const app = require('vn-loopback/server/server'); + +describe('account changePassword()', () => { + it('should throw an error when password does not meet requirements', async() => { + let req = app.models.Account.setPassword(1, 'insecurePass'); + + await expectAsync(req).toBeRejected(); + }); + + it('should update password when it passes requirements', async() => { + let req = app.models.Account.setPassword(1, 'Very$ecurePa22.'); + + await expectAsync(req).toBeResolved(); + }); +}); diff --git a/back/model-config.json b/back/model-config.json index dc5cde217..22d0e2327 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -38,6 +38,9 @@ "ImageCollectionSize": { "dataSource": "vn" }, + "Language": { + "dataSource": "vn" + }, "Province": { "dataSource": "vn" }, diff --git a/back/models/account.js b/back/models/account.js index a0b08dd57..b74a14997 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -4,6 +4,8 @@ module.exports = Self => { require('../methods/account/login')(Self); require('../methods/account/logout')(Self); require('../methods/account/acl')(Self); + require('../methods/account/change-password')(Self); + require('../methods/account/set-password')(Self); require('../methods/account/validate-token')(Self); // Validations diff --git a/back/models/account.json b/back/models/account.json index 7186621b4..fbf736f03 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -24,6 +24,9 @@ "nickname": { "type": "string" }, + "lang": { + "type": "string" + }, "password": { "type": "string", "required": true diff --git a/back/models/language.json b/back/models/language.json new file mode 100644 index 000000000..f4e221464 --- /dev/null +++ b/back/models/language.json @@ -0,0 +1,34 @@ +{ + "name": "Language", + "base": "VnModel", + "options": { + "mysql": { + "table": "hedera.language" + } + }, + "properties": { + "code": { + "type": "string", + "id": true + }, + "name": { + "type": "string", + "required": true + }, + "orgName": { + "type": "string", + "required": true + }, + "isActive": { + "type": "boolean" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-account.sql b/db/changes/10211-accountModule/00-account.sql new file mode 100644 index 000000000..987256793 --- /dev/null +++ b/db/changes/10211-accountModule/00-account.sql @@ -0,0 +1,47 @@ + +ALTER TABLE `account`.`role` + MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL; + +ALTER TABLE `account`.`roleInherit` + ADD UNIQUE( `role`, `inheritsFrom`); + +ALTER TABLE `account`.`roleInherit` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`roleInherit` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + +ALTER TABLE `account`.`mailAlias` + ADD `description` VARCHAR(255) NULL AFTER `alias`; + +ALTER TABLE `account`.`mailAliasAccount` + ADD UNIQUE( `mailAlias`, `account`); + +ALTER TABLE `account`.`mailAliasAccount` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`mailAliasAccount` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + + +USE account; + +DELIMITER $$ + +CREATE TRIGGER role_beforeInsert + BEFORE INSERT ON `role` FOR EACH ROW +BEGIN + CALL role_checkName(NEW.`name`); +END$$ + +CREATE TRIGGER role_beforeUpdate + BEFORE UPDATE ON `role` FOR EACH ROW +BEGIN + IF !(NEW.`name` <=> OLD.`name`) THEN + CALL role_checkName (NEW.`name`); + END IF; +END$$ + +DELIMITER ; \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-myUserChangePassword.sql b/db/changes/10211-accountModule/00-myUserChangePassword.sql new file mode 100644 index 000000000..94fc02087 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserChangePassword.sql @@ -0,0 +1,13 @@ +DROP PROCEDURE IF EXISTS account.myUserChangePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserChangePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * @deprecated Use myUser_changePassword() + */ + CALL myUser_changePassword(vOldPassword, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUserChangePassword TO account@localhost; diff --git a/db/changes/10211-accountModule/00-myUserCheckLogin.sql b/db/changes/10211-accountModule/00-myUserCheckLogin.sql new file mode 100644 index 000000000..eaa962b63 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserCheckLogin.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserCheckLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserCheckLogin`() RETURNS tinyint(1) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_checkLogin() + */ + RETURN myUser_checkLogin(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserCheckLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserGetId.sql b/db/changes/10211-accountModule/00-myUserGetId.sql new file mode 100644 index 000000000..f0bb972aa --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserGetId.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserGetId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getId() + */ + RETURN myUser_getId(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserGetId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserGetName.sql b/db/changes/10211-accountModule/00-myUserGetName.sql new file mode 100644 index 000000000..2f758d0c6 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserGetName.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserGetName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getName() + */ + RETURN myUser_getName(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserGetName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserHasRole.sql b/db/changes/10211-accountModule/00-myUserHasRole.sql new file mode 100644 index 000000000..6d2301328 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserHasRole.sql @@ -0,0 +1,14 @@ +DROP FUNCTION IF EXISTS account.myUserHasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_hasRole() + */ + RETURN myUser_hasRole(vRoleName); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserHasRole TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserHasRoleId.sql b/db/changes/10211-accountModule/00-myUserHasRoleId.sql new file mode 100644 index 000000000..380bd0641 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserHasRoleId.sql @@ -0,0 +1,14 @@ +DROP FUNCTION IF EXISTS account.myUserHasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRoleId`(vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_hasRoleId() + */ + RETURN myUser_hasRoleId(vRoleId); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserHasRoleId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_changePassword.sql b/db/changes/10211-accountModule/00-myUser_changePassword.sql new file mode 100644 index 000000000..3dd86a881 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_changePassword.sql @@ -0,0 +1,17 @@ +DROP PROCEDURE IF EXISTS account.myUser_changePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_changePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * Changes the current user password, if user is in recovery mode ignores the + * current password. + * + * @param vOldPassword The current password + * @param vPassword The new password + */ + CALL user_changePassword(myUser_getId(), vOldPassword, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_changePassword TO account@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_checkLogin.sql b/db/changes/10211-accountModule/00-myUser_checkLogin.sql new file mode 100644 index 000000000..843f57fff --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_checkLogin.sql @@ -0,0 +1,29 @@ +DROP FUNCTION IF EXISTS account.myUser_checkLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_checkLogin`() RETURNS tinyint(1) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * Checks that variables @userId and @userName haven't been altered. + * + * @return %TRUE if they are unaltered or unset, otherwise %FALSE + */ + DECLARE vSignature VARCHAR(128); + DECLARE vKey VARCHAR(255); + + IF @userId IS NOT NULL + AND @userName IS NOT NULL + AND @userSignature IS NOT NULL + THEN + SELECT loginKey INTO vKey FROM userConfig; + SET vSignature = util.hmacSha2(256, CONCAT_WS('/', @userId, @userName), vKey); + RETURN vSignature = @userSignature; + END IF; + + RETURN FALSE; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_checkLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_getId.sql b/db/changes/10211-accountModule/00-myUser_getId.sql new file mode 100644 index 000000000..b3d3f1b28 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_getId.sql @@ -0,0 +1,27 @@ +DROP FUNCTION IF EXISTS account.myUser_getId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * Returns the current user id. + * + * @return The user id + */ + DECLARE vUser INT DEFAULT NULL; + + IF myUser_checkLogin() + THEN + SET vUser = @userId; + ELSE + SELECT id INTO vUser FROM user + WHERE name = LEFT(USER(), INSTR(USER(), '@') - 1); + END IF; + + RETURN vUser; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_getId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_getName.sql b/db/changes/10211-accountModule/00-myUser_getName.sql new file mode 100644 index 000000000..b055227d3 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_getName.sql @@ -0,0 +1,27 @@ +DROP FUNCTION IF EXISTS account.myUser_getName; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * Returns the current user name. + * + * @return The user name + */ + DECLARE vUser VARCHAR(30) DEFAULT NULL; + + IF myUser_checkLogin() + THEN + SET vUser = @userName; + ELSE + SET vUser = LEFT(USER(), INSTR(USER(), '@') - 1); + END IF; + + RETURN vUser; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_getName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_hasRole.sql b/db/changes/10211-accountModule/00-myUser_hasRole.sql new file mode 100644 index 000000000..538b58f08 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_hasRole.sql @@ -0,0 +1,17 @@ +DROP FUNCTION IF EXISTS account.myUser_hasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if current user has/inherits a role. + * + * @param vRoleName Role to check + * @return %TRUE if it has role, %FALSE otherwise + */ + RETURN user_hasRole(myUser_getName(), vRoleName); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_hasRole TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_hasRoleId.sql b/db/changes/10211-accountModule/00-myUser_hasRoleId.sql new file mode 100644 index 000000000..2931443e1 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_hasRoleId.sql @@ -0,0 +1,17 @@ +DROP FUNCTION IF EXISTS account.myUser_hasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRoleId`(vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if current user has/inherits a role. + * + * @param vRoleName Role id to check + * @return %TRUE if it has role, %FALSE otherwise + */ + RETURN user_hasRoleId(myUserGetName(), vRoleId); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_hasRoleId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_login.sql b/db/changes/10211-accountModule/00-myUser_login.sql new file mode 100644 index 000000000..9d92828b0 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_login.sql @@ -0,0 +1,29 @@ +DROP PROCEDURE IF EXISTS account.myUser_login; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_login`(vUserName VARCHAR(255), vPassword VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using the user credentials. + * + * @param vUserName The user name + * @param vPassword The user password + */ + DECLARE vAuthIsOk BOOLEAN DEFAULT FALSE; + + SELECT COUNT(*) = 1 INTO vAuthIsOk FROM user + WHERE name = vUserName + AND password = MD5(vPassword) + AND active; + + IF vAuthIsOk + THEN + CALL myUser_loginWithName (vUserName); + ELSE + CALL util.throw ('INVALID_CREDENTIALS'); + END IF; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_login TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_loginWithKey.sql b/db/changes/10211-accountModule/00-myUser_loginWithKey.sql new file mode 100644 index 000000000..fc12a79d9 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_loginWithKey.sql @@ -0,0 +1,25 @@ +DROP PROCEDURE IF EXISTS account.myUser_loginWithKey; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using the user name and MySQL master key. + * + * @param vUserName The user name + * @param vKey The MySQL master key + */ + DECLARE vLoginKey VARCHAR(255); + + SELECT loginKey INTO vLoginKey FROM userConfig; + + IF vLoginKey = vKey THEN + CALL user_loginWithName(vUserName); + ELSE + CALL util.throw('INVALID_KEY'); + END IF; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_loginWithKey TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_loginWithName.sql b/db/changes/10211-accountModule/00-myUser_loginWithName.sql new file mode 100644 index 000000000..6b86a37f3 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_loginWithName.sql @@ -0,0 +1,26 @@ +DROP PROCEDURE IF EXISTS account.myUser_loginWithName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithName`(vUserName VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using only the user name. This procedure is intended to be executed + * by users with a high level of privileges so that normal users should not have + * execute permissions on it. + * + * @param vUserName The user name + */ + DECLARE vUserId INT DEFAULT NULL; + DECLARE vKey VARCHAR(255); + + SELECT id INTO vUserId FROM user + WHERE name = vUserName; + + SELECT loginKey INTO vKey FROM userConfig; + + SET @userId = vUserId; + SET @userName = vUserName; + SET @userSignature = util.hmacSha2(256, CONCAT_WS('/', vUserId, vUserName), vKey); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-myUser_logout.sql b/db/changes/10211-accountModule/00-myUser_logout.sql new file mode 100644 index 000000000..ffa2c969e --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_logout.sql @@ -0,0 +1,15 @@ +DROP PROCEDURE IF EXISTS account.myUser_logout; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_logout`() +BEGIN +/** + * Logouts the user. + */ + SET @userId = NULL; + SET @userName = NULL; + SET @userSignature = NULL; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_logout TO account@localhost; diff --git a/db/changes/10211-accountModule/00-passwordGenerate.sql b/db/changes/10211-accountModule/00-passwordGenerate.sql new file mode 100644 index 000000000..46048e24d --- /dev/null +++ b/db/changes/10211-accountModule/00-passwordGenerate.sql @@ -0,0 +1,52 @@ +DROP FUNCTION IF EXISTS account.passwordGenerate; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`passwordGenerate`() RETURNS text CHARSET utf8 +BEGIN +/** + * Generates a random password that meets the minimum requirements. + * + * @return Generated password + */ + DECLARE vMinLength TINYINT; + DECLARE vMinAlpha TINYINT; + DECLARE vMinUpper TINYINT; + DECLARE vMinDigits TINYINT; + DECLARE vMinPunct TINYINT; + DECLARE vAlpha TINYINT DEFAULT 0; + DECLARE vUpper TINYINT DEFAULT 0; + DECLARE vDigits TINYINT DEFAULT 0; + DECLARE vPunct TINYINT DEFAULT 0; + DECLARE vRandIndex INT; + DECLARE vPwd TEXT DEFAULT ''; + + DECLARE vAlphaChars TEXT DEFAULT 'abcdefghijklmnopqrstuvwxyz'; + DECLARE vUpperChars TEXT DEFAULT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + DECLARE vDigitChars TEXT DEFAULT '1234567890'; + DECLARE vPunctChars TEXT DEFAULT '!$%&()=.'; + + SELECT length, nAlpha, nUpper, nDigits, nPunct + INTO vMinLength, vMinAlpha, vMinUpper, vMinDigits, vMinPunct FROM userPassword; + + WHILE LENGTH(vPwd) < vMinLength OR vAlpha < vMinAlpha + OR vUpper < vMinUpper OR vDigits < vMinDigits OR vPunct < vMinPunct DO + SET vRandIndex = FLOOR((RAND() * 4) + 1); + + CASE + WHEN vRandIndex = 1 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vAlphaChars, FLOOR((RAND() * 26) + 1), 1)); + SET vAlpha = vAlpha + 1; + WHEN vRandIndex = 2 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vUpperChars, FLOOR((RAND() * 26) + 1), 1)); + SET vUpper = vUpper + 1; + WHEN vRandIndex = 3 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vDigitChars, FLOOR((RAND() * 10) + 1), 1)); + SET vDigits = vDigits + 1; + WHEN vRandIndex = 4 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vPunctChars, FLOOR((RAND() * LENGTH(vPunctChars)) + 1), 1)); + SET vPunct = vPunct + 1; + END CASE; + END WHILE; + RETURN vPwd; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_checkName.sql b/db/changes/10211-accountModule/00-role_checkName.sql new file mode 100644 index 000000000..1e4f31767 --- /dev/null +++ b/db/changes/10211-accountModule/00-role_checkName.sql @@ -0,0 +1,18 @@ +DROP PROCEDURE IF EXISTS account.role_checkName; + +DELIMITER $$ +CREATE PROCEDURE account.role_checkName(vRoleName VARCHAR(255)) +BEGIN +/** + * Checks that role name meets the necessary syntax requirements, otherwise it + * throws an exception. + * Role name must be written in camelCase. + * + * @param vRoleName The role name + */ + IF BINARY vRoleName NOT REGEXP '^[a-z][a-zA-Z]+$' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Role name must be written in camelCase'; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_getDescendents.sql b/db/changes/10211-accountModule/00-role_getDescendents.sql new file mode 100644 index 000000000..9b224f6eb --- /dev/null +++ b/db/changes/10211-accountModule/00-role_getDescendents.sql @@ -0,0 +1,66 @@ +DROP PROCEDURE IF EXISTS account.role_getDescendents; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_getDescendents`(vSelf INT) +BEGIN +/** + * Gets the identifiers of all the subroles implemented by a role (Including + * itself). + * + * @param vSelf The role identifier + * @table tmp.role Subroles implemented by the role + */ + DECLARE vIsRoot BOOL; + + DROP TEMPORARY TABLE IF EXISTS + tmp.role, parents, childs; + + CREATE TEMPORARY TABLE tmp.role + (UNIQUE (id)) + ENGINE = MEMORY + SELECT vSelf AS id; + + CREATE TEMPORARY TABLE parents + ENGINE = MEMORY + SELECT vSelf AS id; + + CREATE TEMPORARY TABLE childs + LIKE parents; + + REPEAT + DELETE FROM childs; + INSERT INTO childs + SELECT DISTINCT r.inheritsFrom id + FROM parents p + JOIN roleInherit r ON r.role = p.id + LEFT JOIN tmp.role t ON t.id = r.inheritsFrom + WHERE t.id IS NULL; + + DELETE FROM parents; + INSERT INTO parents + SELECT * FROM childs; + + INSERT INTO tmp.role + SELECT * FROM childs; + + UNTIL ROW_COUNT() <= 0 + END REPEAT; + + -- If it is root all the roles are added + + SELECT COUNT(*) > 0 INTO vIsRoot + FROM tmp.role t + JOIN role r ON r.id = t.id + WHERE r.`name` = 'root'; + + IF vIsRoot THEN + INSERT IGNORE INTO tmp.role (id) + SELECT id FROM role; + END IF; + + -- Cleaning + + DROP TEMPORARY TABLE + parents, childs; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_sync.sql b/db/changes/10211-accountModule/00-role_sync.sql new file mode 100644 index 000000000..8e16ef567 --- /dev/null +++ b/db/changes/10211-accountModule/00-role_sync.sql @@ -0,0 +1,53 @@ +DROP PROCEDURE IF EXISTS account.role_sync; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_sync`() +BEGIN +/** + * Synchronize the @roleRole table with the current role hierarchy. This + * procedure must be called every time the @roleInherit table is modified so + * that the changes made on it are effective. + */ + DECLARE vRoleId INT; + DECLARE vDone BOOL; + + DECLARE cur CURSOR FOR + SELECT id FROM role; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + DROP TEMPORARY TABLE IF EXISTS tRoleRole; + CREATE TEMPORARY TABLE tRoleRole + ENGINE = MEMORY + SELECT * FROM roleRole LIMIT 0; + + OPEN cur; + + l: LOOP + SET vDone = FALSE; + FETCH cur INTO vRoleId; + + IF vDone THEN + LEAVE l; + END IF; + + CALL role_getDescendents(vRoleId); + + INSERT INTO tRoleRole (role, inheritsFrom) + SELECT vRoleId, id FROM tmp.role; + + DROP TEMPORARY TABLE tmp.role; + END LOOP; + + CLOSE cur; + + START TRANSACTION; + DELETE FROM roleRole; + INSERT INTO roleRole SELECT * FROM tRoleRole; + COMMIT; + + DROP TEMPORARY TABLE tRoleRole; + + CALL role_syncPrivileges; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_syncPrivileges.sql b/db/changes/10211-accountModule/00-role_syncPrivileges.sql new file mode 100644 index 000000000..0d6d8975b --- /dev/null +++ b/db/changes/10211-accountModule/00-role_syncPrivileges.sql @@ -0,0 +1,494 @@ +DROP PROCEDURE IF EXISTS account.role_syncPrivileges; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_syncPrivileges`() +BEGIN +/** + * Synchronizes permissions of MySQL role users based on role hierarchy. + * The computed role users of permission mix will be named according to + * pattern z-[role_name]. + * + * If any@localhost user exists, it will be taken as a template for basic + * attributes. + * + * Warning! This procedure should only be called when MySQL privileges + * are modified. If role hierarchy is modified, you must call the role_sync() + * procedure wich calls this internally. + */ + DECLARE vIsMysql BOOL DEFAULT VERSION() NOT LIKE '%MariaDB%'; + DECLARE vVersion INT DEFAULT SUBSTRING_INDEX(VERSION(), '.', 1); + DECLARE vTplUser VARCHAR(255) DEFAULT 'any'; + DECLARE vTplHost VARCHAR(255) DEFAULT '%'; + DECLARE vRoleHost VARCHAR(255) DEFAULT 'localhost'; + DECLARE vAllHost VARCHAR(255) DEFAULT '%'; + DECLARE vPrefix VARCHAR(2) DEFAULT 'z-'; + DECLARE vPrefixedLike VARCHAR(255); + DECLARE vPassword VARCHAR(255) DEFAULT ''; + + -- Deletes computed role users + + SET vPrefixedLike = CONCAT(vPrefix, '%'); + + DELETE FROM mysql.user + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.db + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.tables_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.columns_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.procs_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.proxies_priv + WHERE `Proxied_user` LIKE vPrefixedLike; + + -- Temporary tables + + DROP TEMPORARY TABLE IF EXISTS tRole; + CREATE TEMPORARY TABLE tRole + (INDEX (id)) + ENGINE = MEMORY + SELECT + id, + `name` role, + CONCAT(vPrefix, `name`) prefixedRole + FROM role + WHERE hasLogin; + + DROP TEMPORARY TABLE IF EXISTS tRoleInherit; + CREATE TEMPORARY TABLE tRoleInherit + (INDEX (inheritsFrom)) + ENGINE = MEMORY + SELECT + r.prefixedRole, + ri.`name` inheritsFrom + FROM tRole r + JOIN roleRole rr ON rr.role = r.id + JOIN role ri ON ri.id = rr.inheritsFrom; + + -- Recreate role users + + IF vIsMysql THEN + DROP TEMPORARY TABLE IF EXISTS tUser; + CREATE TEMPORARY TABLE tUser + SELECT + r.prefixedRole `User`, + vTplHost `Host`, + IFNULL(t.`authentication_string`, + '') `authentication_string`, + IFNULL(t.`plugin`, + 'mysql_native_password') `plugin`, + IFNULL(IF('' != u.`ssl_type`, + u.`ssl_type`, t.`ssl_type`), + '') `ssl_type`, + IFNULL(IF('' != u.`ssl_cipher`, + u.`ssl_cipher`, t.`ssl_cipher`), + '') `ssl_cipher`, + IFNULL(IF('' != u.`x509_issuer`, + u.`x509_issuer`, t.`x509_issuer`), + '') `x509_issuer`, + IFNULL(IF('' != u.`x509_subject`, + u.`x509_subject`, t.`x509_subject`), + '') `x509_subject`, + IFNULL(IF(0 != u.`max_questions`, + u.`max_questions`, t.`max_questions`), + 0) `max_questions`, + IFNULL(IF(0 != u.`max_updates`, + u.`max_updates`, t.`max_updates`), + 0) `max_updates`, + IFNULL(IF(0 != u.`max_connections`, + u.`max_connections`, t.`max_connections`), + 0) `max_connections`, + IFNULL(IF(0 != u.`max_user_connections`, + u.`max_user_connections`, t.`max_user_connections`), + 0) `max_user_connections` + FROM tRole r + LEFT JOIN mysql.user t + ON t.`User` = vTplUser + AND t.`Host` = vRoleHost + LEFT JOIN mysql.user u + ON u.`User` = r.role + AND u.`Host` = vRoleHost; + + IF vVersion <= 5 THEN + SELECT `Password` INTO vPassword + FROM mysql.user + WHERE `User` = vTplUser + AND `Host` = vRoleHost; + + INSERT INTO mysql.user ( + `User`, + `Host`, + `Password`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + ) + SELECT + `User`, + `Host`, + vPassword, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + FROM tUser; + ELSE + INSERT INTO mysql.user ( + `User`, + `Host`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + ) + SELECT + `User`, + `Host`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + FROM tUser; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tUser; + ELSE + INSERT INTO mysql.global_priv ( + `User`, + `Host`, + `Priv` + ) + SELECT + r.prefixedRole, + vTplHost, + JSON_MERGE_PATCH( + IFNULL(t.`Priv`, '{}'), + IFNULL(u.`Priv`, '{}') + ) + FROM tRole r + LEFT JOIN mysql.global_priv t + ON t.`User` = vTplUser + AND t.`Host` = vRoleHost + LEFT JOIN mysql.global_priv u + ON u.`User` = r.role + AND u.`Host` = vRoleHost; + END IF; + + INSERT INTO mysql.proxies_priv ( + `User`, + `Host`, + `Proxied_user`, + `Proxied_host`, + `Grantor` + ) + SELECT + '', + vAllHost, + prefixedRole, + vTplHost, + CONCAT(prefixedRole, '@', vTplHost) + FROM tRole; + + -- Copies global privileges + + DROP TEMPORARY TABLE IF EXISTS tUserPriv; + + IF vIsMysql THEN + CREATE TEMPORARY TABLE tUserPriv + (INDEX (prefixedRole)) + ENGINE = MEMORY + SELECT + r.prefixedRole, + MAX(u.`Select_priv`) `Select_priv`, + MAX(u.`Insert_priv`) `Insert_priv`, + MAX(u.`Update_priv`) `Update_priv`, + MAX(u.`Delete_priv`) `Delete_priv`, + MAX(u.`Create_priv`) `Create_priv`, + MAX(u.`Drop_priv`) `Drop_priv`, + MAX(u.`Reload_priv`) `Reload_priv`, + MAX(u.`Shutdown_priv`) `Shutdown_priv`, + MAX(u.`Process_priv`) `Process_priv`, + MAX(u.`File_priv`) `File_priv`, + MAX(u.`Grant_priv`) `Grant_priv`, + MAX(u.`References_priv`) `References_priv`, + MAX(u.`Index_priv`) `Index_priv`, + MAX(u.`Alter_priv`) `Alter_priv`, + MAX(u.`Show_db_priv`) `Show_db_priv`, + MAX(u.`Super_priv`) `Super_priv`, + MAX(u.`Create_tmp_table_priv`) `Create_tmp_table_priv`, + MAX(u.`Lock_tables_priv`) `Lock_tables_priv`, + MAX(u.`Execute_priv`) `Execute_priv`, + MAX(u.`Repl_slave_priv`) `Repl_slave_priv`, + MAX(u.`Repl_client_priv`) `Repl_client_priv`, + MAX(u.`Create_view_priv`) `Create_view_priv`, + MAX(u.`Show_view_priv`) `Show_view_priv`, + MAX(u.`Create_routine_priv`) `Create_routine_priv`, + MAX(u.`Alter_routine_priv`) `Alter_routine_priv`, + MAX(u.`Create_user_priv`) `Create_user_priv`, + MAX(u.`Event_priv`) `Event_priv`, + MAX(u.`Trigger_priv`) `Trigger_priv`, + MAX(u.`Create_tablespace_priv`) `Create_tablespace_priv` + FROM tRoleInherit r + JOIN mysql.user u + ON u.`User` = r.inheritsFrom + AND u.`Host`= vRoleHost + GROUP BY r.prefixedRole; + + UPDATE mysql.user u + JOIN tUserPriv t + ON u.`User` = t.prefixedRole + AND u.`Host` = vTplHost + SET + u.`Select_priv` + = t.`Select_priv`, + u.`Insert_priv` + = t.`Insert_priv`, + u.`Update_priv` + = t.`Update_priv`, + u.`Delete_priv` + = t.`Delete_priv`, + u.`Create_priv` + = t.`Create_priv`, + u.`Drop_priv` + = t.`Drop_priv`, + u.`Reload_priv` + = t.`Reload_priv`, + u.`Shutdown_priv` + = t.`Shutdown_priv`, + u.`Process_priv` + = t.`Process_priv`, + u.`File_priv` + = t.`File_priv`, + u.`Grant_priv` + = t.`Grant_priv`, + u.`References_priv` + = t.`References_priv`, + u.`Index_priv` + = t.`Index_priv`, + u.`Alter_priv` + = t.`Alter_priv`, + u.`Show_db_priv` + = t.`Show_db_priv`, + u.`Super_priv` + = t.`Super_priv`, + u.`Create_tmp_table_priv` + = t.`Create_tmp_table_priv`, + u.`Lock_tables_priv` + = t.`Lock_tables_priv`, + u.`Execute_priv` + = t.`Execute_priv`, + u.`Repl_slave_priv` + = t.`Repl_slave_priv`, + u.`Repl_client_priv` + = t.`Repl_client_priv`, + u.`Create_view_priv` + = t.`Create_view_priv`, + u.`Show_view_priv` + = t.`Show_view_priv`, + u.`Create_routine_priv` + = t.`Create_routine_priv`, + u.`Alter_routine_priv` + = t.`Alter_routine_priv`, + u.`Create_user_priv` + = t.`Create_user_priv`, + u.`Event_priv` + = t.`Event_priv`, + u.`Trigger_priv` + = t.`Trigger_priv`, + u.`Create_tablespace_priv` + = t.`Create_tablespace_priv`; + ELSE + CREATE TEMPORARY TABLE tUserPriv + (INDEX (prefixedRole)) + SELECT + r.prefixedRole, + BIT_OR(JSON_VALUE(p.`Priv`, '$.access')) access + FROM tRoleInherit r + JOIN mysql.global_priv p + ON p.`User` = r.inheritsFrom + AND p.`Host`= vRoleHost + GROUP BY r.prefixedRole; + + UPDATE mysql.global_priv p + JOIN tUserPriv t + ON p.`User` = t.prefixedRole + AND p.`Host` = vTplHost + SET + p.`Priv` = JSON_SET(p.`Priv`, '$.access', t.access); + END IF; + + DROP TEMPORARY TABLE tUserPriv; + + -- Copy schema level privileges + + INSERT INTO mysql.db ( + `User`, + `Host`, + `Db`, + `Select_priv`, + `Insert_priv`, + `Update_priv`, + `Delete_priv`, + `Create_priv`, + `Drop_priv`, + `Grant_priv`, + `References_priv`, + `Index_priv`, + `Alter_priv`, + `Create_tmp_table_priv`, + `Lock_tables_priv`, + `Create_view_priv`, + `Show_view_priv`, + `Create_routine_priv`, + `Alter_routine_priv`, + `Execute_priv`, + `Event_priv`, + `Trigger_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + MAX(t.`Select_priv`), + MAX(t.`Insert_priv`), + MAX(t.`Update_priv`), + MAX(t.`Delete_priv`), + MAX(t.`Create_priv`), + MAX(t.`Drop_priv`), + MAX(t.`Grant_priv`), + MAX(t.`References_priv`), + MAX(t.`Index_priv`), + MAX(t.`Alter_priv`), + MAX(t.`Create_tmp_table_priv`), + MAX(t.`Lock_tables_priv`), + MAX(t.`Create_view_priv`), + MAX(t.`Show_view_priv`), + MAX(t.`Create_routine_priv`), + MAX(t.`Alter_routine_priv`), + MAX(t.`Execute_priv`), + MAX(t.`Event_priv`), + MAX(t.`Trigger_priv`) + FROM tRoleInherit r + JOIN mysql.db t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`; + + -- Copy table level privileges + + INSERT INTO mysql.tables_priv ( + `User`, + `Host`, + `Db`, + `Table_name`, + `Grantor`, + `Timestamp`, + `Table_priv`, + `Column_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Table_name`, + t.`Grantor`, + MAX(t.`Timestamp`), + IFNULL(GROUP_CONCAT(NULLIF(t.`Table_priv`, '')), ''), + IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '') + FROM tRoleInherit r + JOIN mysql.tables_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`; + + -- Copy column level privileges + + INSERT INTO mysql.columns_priv ( + `User`, + `Host`, + `Db`, + `Table_name`, + `Column_name`, + `Timestamp`, + `Column_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Table_name`, + t.`Column_name`, + MAX(t.`Timestamp`), + IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '') + FROM tRoleInherit r + JOIN mysql.columns_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`, t.`Column_name`; + + -- Copy routine privileges + + INSERT IGNORE INTO mysql.procs_priv ( + `User`, + `Host`, + `Db`, + `Routine_name`, + `Routine_type`, + `Grantor`, + `Timestamp`, + `Proc_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Routine_name`, + t.`Routine_type`, + t.`Grantor`, + t.`Timestamp`, + t.`Proc_priv` + FROM tRoleInherit r + JOIN mysql.procs_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost; + + -- Free memory + + DROP TEMPORARY TABLE + tRole, + tRoleInherit; + + FLUSH PRIVILEGES; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userGetId.sql b/db/changes/10211-accountModule/00-userGetId.sql new file mode 100644 index 000000000..219ea680c --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetId.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.userGetId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getId() + */ + RETURN myUser_getId(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.userGetId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userGetMysqlRole.sql b/db/changes/10211-accountModule/00-userGetMysqlRole.sql new file mode 100644 index 000000000..673f1aac9 --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetMysqlRole.sql @@ -0,0 +1,11 @@ +DROP FUNCTION IF EXISTS account.userGetMysqlRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8 +BEGIN +/** + * @deprecated Use user_getMysqlRole() + */ + RETURN user_getMysqlRole(); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userGetName.sql b/db/changes/10211-accountModule/00-userGetName.sql new file mode 100644 index 000000000..f49f2dbef --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetName.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.userGetName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getName() + */ + RETURN myUser_getName(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.userGetName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userGetNameFromId.sql b/db/changes/10211-accountModule/00-userGetNameFromId.sql new file mode 100644 index 000000000..f8e9333cb --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetNameFromId.sql @@ -0,0 +1,11 @@ +DROP FUNCTION IF EXISTS account.userGetNameFromId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8 +BEGIN +/** + * @deprecated Use user_getNameFromId(); + */ + RETURN user_getNameFromId(vSelf); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userHasRole.sql b/db/changes/10211-accountModule/00-userHasRole.sql new file mode 100644 index 000000000..3e09d27bf --- /dev/null +++ b/db/changes/10211-accountModule/00-userHasRole.sql @@ -0,0 +1,12 @@ +DROP FUNCTION IF EXISTS account.userHasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use user_hasRole() + */ + RETURN user_hasRole(vUserName, vRoleName); +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-userHasRoleId.sql b/db/changes/10211-accountModule/00-userHasRoleId.sql new file mode 100644 index 000000000..9fcd9f073 --- /dev/null +++ b/db/changes/10211-accountModule/00-userHasRoleId.sql @@ -0,0 +1,12 @@ +DROP FUNCTION IF EXISTS account.userHasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use user_hasRoleId() + */ + RETURN user_hasRoleId(vUser, vRoleId); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userLogin.sql b/db/changes/10211-accountModule/00-userLogin.sql new file mode 100644 index 000000000..63a332254 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLogin.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.userLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLogin`(vUserName VARCHAR(255), vPassword VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_login() + */ + CALL myUser_login(vUserName, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.userLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userLoginWithKey.sql b/db/changes/10211-accountModule/00-userLoginWithKey.sql new file mode 100644 index 000000000..45a490c71 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLoginWithKey.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.userLoginWithKey; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_loginWithKey() + */ + CALL myUser_loginWithKey(vUserName, vKey); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.userLoginWithKey TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userLoginWithName.sql b/db/changes/10211-accountModule/00-userLoginWithName.sql new file mode 100644 index 000000000..4053970e4 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLoginWithName.sql @@ -0,0 +1,12 @@ +DROP PROCEDURE IF EXISTS account.userLoginWithName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithName`(vUserName VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_loginWithName() + */ + CALL myUser_loginWithName(vUserName); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userLogout.sql b/db/changes/10211-accountModule/00-userLogout.sql new file mode 100644 index 000000000..7d0d68324 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLogout.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.myUserLogout; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserLogout`() +BEGIN +/** + * @deprecated Use myUser_Logout() + */ + CALL myUser_logout; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUserLogout TO account@localhost; diff --git a/db/changes/10211-accountModule/00-userSetPassword.sql b/db/changes/10211-accountModule/00-userSetPassword.sql new file mode 100644 index 000000000..fd3daec53 --- /dev/null +++ b/db/changes/10211-accountModule/00-userSetPassword.sql @@ -0,0 +1,16 @@ +DROP PROCEDURE IF EXISTS account.userSetPassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userSetPassword`(vUserName VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * @deprecated Use user_setPassword() + */ + DECLARE vUserId INT; + + SELECT id INTO vUserId + FROM user WHERE `name` = vUserName; + + CALL user_setPassword(vUserId, vPassword); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_changePassword.sql b/db/changes/10211-accountModule/00-user_changePassword.sql new file mode 100644 index 000000000..c137213e0 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_changePassword.sql @@ -0,0 +1,27 @@ +DROP PROCEDURE IF EXISTS account.user_changePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE account.user_changePassword(vSelf INT, vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * Changes the user password. + * + * @param vSelf The user id + * @param vOldPassword The current password + * @param vPassword The new password + */ + DECLARE vPasswordOk BOOL; + DECLARE vUserName VARCHAR(255); + + SELECT `password` = MD5(vOldPassword), `name` + INTO vPasswordOk, vUserName + FROM user WHERE id = vSelf; + + IF NOT vPasswordOk THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Invalid password'; + END IF; + + CALL user_setPassword(vSelf, vPassword); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_checkName.sql b/db/changes/10211-accountModule/00-user_checkName.sql new file mode 100644 index 000000000..9b54d6175 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_checkName.sql @@ -0,0 +1,17 @@ +DROP PROCEDURE IF EXISTS account.user_checkName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`user_checkName`(vUserName VARCHAR(255)) +BEGIN +/** + * Checks that username meets the necessary syntax requirements, otherwise it + * throws an exception. + * The user name must only contain lowercase letters or, starting with second + * character, numbers or underscores. + */ + IF vUserName NOT REGEXP '^[a-z0-9_-]*$' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'INVALID_USER_NAME'; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_getMysqlRole.sql b/db/changes/10211-accountModule/00-user_getMysqlRole.sql new file mode 100644 index 000000000..4088ea8a4 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_getMysqlRole.sql @@ -0,0 +1,22 @@ +DROP FUNCTION IF EXISTS account.user_getMysqlRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8 +BEGIN +/** + * From a username, it returns the associated MySQL wich should be used when + * using external authentication systems. + * + * @param vUserName The user name + * @return The associated MySQL role + */ + DECLARE vRole VARCHAR(255); + + SELECT CONCAT(IF(r.hasLogin, 'z-', ''), r.name) INTO vRole + FROM role r + JOIN user u ON u.role = r.id + WHERE u.name = vUserName; + + RETURN vRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_getNameFromId.sql b/db/changes/10211-accountModule/00-user_getNameFromId.sql new file mode 100644 index 000000000..ae9ae5941 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_getNameFromId.sql @@ -0,0 +1,20 @@ +DROP FUNCTION IF EXISTS account.user_getNameFromId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8 +BEGIN +/** + * Gets user name from it's id. + * + * @param vSelf The user id + * @return The user name + */ + DECLARE vName VARCHAR(30); + + SELECT `name` INTO vName + FROM user + WHERE id = vId; + + RETURN vSelf; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_hasRole.sql b/db/changes/10211-accountModule/00-user_hasRole.sql new file mode 100644 index 000000000..d42c81deb --- /dev/null +++ b/db/changes/10211-accountModule/00-user_hasRole.sql @@ -0,0 +1,25 @@ +DROP FUNCTION IF EXISTS account.user_hasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if user has/inherits a role. + * + * @param vUserName The user name + * @param vRoleName Role to check + * @return %TRUE if it has role, %FALSE otherwise + */ + DECLARE vHasRole BOOL DEFAULT FALSE; + + SELECT COUNT(*) > 0 INTO vHasRole + FROM user u + JOIN roleRole rr ON rr.role = u.role + JOIN role r ON r.id = rr.inheritsFrom + WHERE u.`name` = vUserName + AND r.`name` = vRoleName COLLATE 'utf8_unicode_ci'; + + RETURN vHasRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_hasRoleId.sql b/db/changes/10211-accountModule/00-user_hasRoleId.sql new file mode 100644 index 000000000..b2f523e8c --- /dev/null +++ b/db/changes/10211-accountModule/00-user_hasRoleId.sql @@ -0,0 +1,25 @@ +DROP FUNCTION IF EXISTS account.user_hasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if user has/inherits a role. + * + * @param vUserName The user name + * @param vRoleId Role id to check + * @return %TRUE if it has role, %FALSE otherwise + */ + DECLARE vHasRole BOOL DEFAULT FALSE; + + SELECT COUNT(*) > 0 INTO vHasRole + FROM user u + JOIN roleRole rr ON rr.role = u.role + JOIN role r ON r.id = rr.inheritsFrom + WHERE u.`name` = vUser + AND r.id = vRoleId; + + RETURN vHasRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_setPassword.sql b/db/changes/10211-accountModule/00-user_setPassword.sql new file mode 100644 index 000000000..430c60eab --- /dev/null +++ b/db/changes/10211-accountModule/00-user_setPassword.sql @@ -0,0 +1,23 @@ +DROP PROCEDURE IF EXISTS account.user_setPassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE account.user_setPassword(vSelf INT, vPassword VARCHAR(255)) +BEGIN +/** + * Change the password of the passed as a parameter. Only administrators should + * have execute privileges on the procedure since it does not request the user's + * current password. + * + * @param vSelf The user id + * @param vPassword New password + */ + CALL user_checkPassword(vPassword); + + UPDATE user SET + `password` = MD5(vPassword), + `recoverPass` = FALSE + WHERE id = vSelf; + + CALL user_syncPassword(vSelf, vPassword); +END$$ +DELIMITER ; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 56f779e55..d339506db 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -12,10 +12,6 @@ INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) VALUES ('1', '6'); -INSERT INTO `account`.`mailConfig` (`id`, `domain`) - VALUES - ('1', 'verdnatura.es'); - INSERT INTO `vn`.`bionicConfig` (`generalInflationCoeficient`, `minimumDensityVolumetricWeight`, `verdnaturaVolumeBox`, `itemCarryBox`) VALUES (1.30, 167.00, 138000, 71); @@ -33,6 +29,7 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`) ('10'); UPDATE `account`.`role` SET id = 100 WHERE id = 0; +CALL `account`.`role_sync`; INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`) SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en' @@ -52,7 +49,7 @@ DELETE FROM `vn`.`worker` WHERE firstName ='customer'; INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType`, `maxAmount`, `employeeFk`, `testUrl`) VALUES (1, 978, 1, 0, 2000, 9, 0); - + INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`) VALUES (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'), @@ -68,6 +65,36 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`, (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'), (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'); + +INSERT INTO `account`.`userPassword` (`id`, `length`, `nAlpha`, `nUpper`, `nDigits`, `nPunct`) + VALUES + (1, 8, 1, 1, 1, 1); + +INSERT INTO `account`.`account`(`id`) + VALUES + (101), + (102); + +INSERT INTO `account`.`mailConfig` (`id`, `domain`) + VALUES + (1, 'verdnatura.es'); + +INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) + VALUES + (1, 'general', 'General mailing list', FALSE), + (2, 'it' , 'IT department' , TRUE), + (3, 'sales' , 'Sales department' , TRUE); + +INSERT INTO `account`.`mailAliasAccount`(`mailAlias`, `account`) + VALUES + (1, 101), + (1, 102), + (2, 101); + +INSERT INTO `account`.`mailForward`(`account`, `forwardTo`) + VALUES + (101, 'employee@domain.local'); + INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) VALUES (106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106), @@ -88,6 +115,15 @@ INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, (19,'Francia', 1, 'FR', 1, 27), (30,'Canarias', 1, 'IC', 1, 24); +INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`) + VALUES + ('ca', 'Català' , 'Catalan' , TRUE), + ('en', 'English' , 'English' , TRUE), + ('es', 'Español' , 'Spanish' , TRUE), + ('fr', 'Français' , 'French' , TRUE), + ('mn', 'Португалий', 'Mongolian' , TRUE), + ('pt', 'Português' , 'Portuguese', TRUE); + INSERT INTO `vn`.`warehouseAlias`(`id`, `name`) VALUES (1, 'Main Warehouse'), diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js index 9cfa3b410..4994e1547 100644 --- a/front/core/components/crud-model/crud-model.js +++ b/front/core/components/crud-model/crud-model.js @@ -134,7 +134,7 @@ export default class CrudModel extends ModelProxy { */ save() { if (!this.isChanged) - return null; + return this.$q.resolve(); let deletes = []; let updates = []; diff --git a/front/core/components/data-viewer/index.html b/front/core/components/data-viewer/index.html index 8c843d869..3488b7b0b 100644 --- a/front/core/components/data-viewer/index.html +++ b/front/core/components/data-viewer/index.html @@ -1,6 +1,7 @@
diff --git a/front/core/components/list/style.scss b/front/core/components/list/style.scss index 223748f4a..adbc496c7 100644 --- a/front/core/components/list/style.scss +++ b/front/core/components/list/style.scss @@ -32,7 +32,6 @@ vn-list, vn-item, .vn-item { - @extend %clickable; display: flex; align-items: center; color: inherit; @@ -85,4 +84,6 @@ vn-item, } } - +a.vn-item { + @extend %clickable; +} diff --git a/front/core/components/menu/menu.js b/front/core/components/menu/menu.js index 3eb169926..d0c649004 100755 --- a/front/core/components/menu/menu.js +++ b/front/core/components/menu/menu.js @@ -1,5 +1,6 @@ import ngModule from '../../module'; import Popover from '../popover'; +import './style.scss'; export default class Menu extends Popover { show(parent) { diff --git a/front/core/components/menu/style.scss b/front/core/components/menu/style.scss new file mode 100644 index 000000000..92f437243 --- /dev/null +++ b/front/core/components/menu/style.scss @@ -0,0 +1,7 @@ +@import "./effects"; + +.vn-menu { + vn-item, .vn-item { + @extend %clickable; + } +} diff --git a/front/core/components/model-proxy/model-proxy.js b/front/core/components/model-proxy/model-proxy.js index 592b25710..26c28c803 100644 --- a/front/core/components/model-proxy/model-proxy.js +++ b/front/core/components/model-proxy/model-proxy.js @@ -107,15 +107,16 @@ export default class ModelProxy extends DataModel { * Removes a row from the model and emits the 'rowRemove' event. * * @param {Number} index The row index + * @return {Promise} The save request promise */ remove(index) { - let [item] = this.data.splice(index, 1); + let [row] = this.data.splice(index, 1); - let proxiedIndex = this.proxiedData.indexOf(item); + let proxiedIndex = this.proxiedData.indexOf(row); this.proxiedData.splice(proxiedIndex, 1); - if (!item.$isNew) - this.removed.push(item); + if (!row.$isNew) + this.removed.push(row); this.isChanged = true; if (!this.data.length) @@ -125,7 +126,19 @@ export default class ModelProxy extends DataModel { this.emit('dataUpdate'); if (this.autoSave) - this.save(); + return this.save(); + else + return this.$q.resolve(); + } + + /** + * Removes a row from the model and emits the 'rowRemove' event. + * + * @param {Object} row The row object + * @return {Promise} The save request promise + */ + removeRow(row) { + return this.remove(this.data.indexOf(row)); } /** diff --git a/front/core/components/searchbar/locale/en.yml b/front/core/components/searchbar/locale/en.yml index 52ab6a184..e5b562c24 100644 --- a/front/core/components/searchbar/locale/en.yml +++ b/front/core/components/searchbar/locale/en.yml @@ -1 +1 @@ -Search by: Search by {{module | translate}} \ No newline at end of file +Search for: Search {{module}} \ No newline at end of file diff --git a/front/core/components/searchbar/locale/es.yml b/front/core/components/searchbar/locale/es.yml index 730564a7b..53a5bb289 100644 --- a/front/core/components/searchbar/locale/es.yml +++ b/front/core/components/searchbar/locale/es.yml @@ -1 +1 @@ -Search by: Buscar por {{module | translate}} \ No newline at end of file +Search for: Buscar {{module}} \ No newline at end of file diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 0b7bd8cd2..58fdadb65 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -16,6 +16,7 @@ import './style.scss'; * @property {Function} onSearch Function to call when search is submited * @property {CrudModel} model The model used for searching * @property {Function} exprBuilder If defined, is used to build each non-null param expresion + * @property {String} baseState The base state for searchs */ export default class Searchbar extends Component { constructor($element, $) { @@ -23,6 +24,8 @@ export default class Searchbar extends Component { this.searchState = '.'; this.placeholder = 'Search'; this.autoState = true; + this.separateIndex = true; + this.entityState = 'card.summary'; this.deregisterCallback = this.$transitions.onSuccess( {}, transition => this.onStateChange(transition)); @@ -33,11 +36,13 @@ export default class Searchbar extends Component { if (!this.baseState) { let stateParts = this.$state.current.name.split('.'); this.baseState = stateParts[0]; - } + this.searchState = `${this.baseState}.index`; + } else + this.searchState = this.baseState; - this.searchState = `${this.baseState}.index`; - this.placeholder = this.$t('Search by', { - module: this.baseState + let description = this.$state.get(this.baseState).description; + this.placeholder = this.$t('Search for', { + module: this.$t(description).toLowerCase() }); } @@ -205,7 +210,9 @@ export default class Searchbar extends Component { && source != 'state' && !angular.equals(filter, {}) && data - && data.length == 1; + && data.length == 1 + && filter.search + && Object.keys(filter).length == 1; if (isOneResult) { let baseDepth = this.baseState.split('.').length; @@ -222,7 +229,7 @@ export default class Searchbar extends Component { subState += '.index'; break; default: - subState = 'card.summary'; + subState = this.entityState; } if (this.stateParams) @@ -292,8 +299,10 @@ ngModule.vnComponent('vnSearchbar', { panel: '@', info: '@?', onSearch: '&?', - baseState: '@?', autoState: ' this.callback(transition)); - this.updateOriginalData(); + this.snapshot(); } $onInit() { - if (this.get && this.url) - this.fetchData(); - else if (this.get && !this.url) - throw new Error('URL parameter ommitted'); - } + let fetch = !this.insertMode + && this.get + && this.url + && this.idValue; - $onChanges() { - if (this.data) - this.updateOriginalData(); + if (fetch) + this.fetch(); + else { + this.isNew = !!this.insertMode; + this.snapshot(); + } } $onDestroy() { this.deregisterCallback(); } + /** + * @type {Booelan} The computed instance HTTP path + */ + get instanceUrl() { + return `${this.url}/${this.idValue}`; + } + + /** + * @type {Object} The instance data + */ + get data() { + return this._data; + } + + set data(value) { + this._data = value; + this.isNew = !!this.insertMode; + this.snapshot(); + } + + /** + * @type {Booelan} Whether it's popullated with data + */ + get hasData() { + return !!this.data; + } + + set hasData(value) { + if (value) + this.fill(); + else + this.delete(); + } + + /** + * @type {Booelan} Whether instance data have been modified + */ get dirty() { return this.form && this.form.$dirty || this.dataChanged(); } - dataChanged() { - let data = this.copyInNewObject(this.data); - return !isEqual(data, this.orgData); + fetch() { + let filter = mergeFilters({ + fields: this.fields, + where: this.where, + include: this.include + }, this.filter); + + let params = filter ? {filter} : null; + + return this.$http.get(this.instanceUrl, params) + .then(json => { + this.overwrite(json.data); + this.isNew = false; + this.snapshot(); + }) + .catch(err => { + if (!(err.name == 'HttpError' && err.status == 404)) + throw err; + + if (this.autoFill) { + this.insert(); + this.snapshot(); + } + }); } - fetchData() { - let id = this.data[this.idField]; - return this.$http.get(`${this.url}/${id}`).then( - json => { - this.data = copyObject(json.data); - this.updateOriginalData(); - } - ); + insert(data) { + this.assign({[this.idField]: this.idValue}, data); + this.isNew = true; + this.deleted = null; + } + + delete() { + if (!this.hasData) return; + this.deleted = this.makeSnapshot(); + this.clear(); + this.isNew = false; + } + + recover() { + if (!this.deleted) return; + this.restoreSnapshot(this.deleted); + } + + fill() { + if (this.hasData) + return; + + if (this.deleted) + this.recover(); + else if (this.original && this.original.data) + this.reset(); + else + this.insert(); + } + + reset() { + this.restoreSnapshot(this.original); + this.setPristine(); + } + + snapshot() { + const snapshot = this.makeSnapshot(); + + if (snapshot.data) { + const idValue = snapshot.data[this.idField]; + if (idValue) this.idValue = idValue; + } + + this.original = snapshot; + this.orgData = snapshot.data; + this.deleted = null; + this.setPristine(); + } + + makeSnapshot() { + return { + data: this.copyData(), + isNew: this.isNew, + ref: this.data + }; + } + + restoreSnapshot(snapshot) { + if (!snapshot) return; + this._data = snapshot.ref; + this.overwrite(snapshot.data); + this.isNew = snapshot.isNew; + this.deleted = null; + } + + writeData(res) { + if (this.hasData) + this.assign(res.data); + this.isNew = false; + this.snapshot(); + return res; + } + + clear() { + this._data = null; + } + + overwrite(data) { + if (data) { + if (!this.data) this._data = {}; + overwrite(this.data, data); + } else + this._data = null; + } + + assign(...args) { + this._data = Object.assign(this.data || {}, ...args); + } + + copyData() { + return copyObject(this.data); + } + + refresh() { + return this.fetch(); + } + + dataChanged() { + return !isEqual(this.orgData, this.copyData()); } /** @@ -100,9 +250,10 @@ export default class Watcher extends Component { */ submit() { try { - if (this.requestMethod() !== 'post') + if (this.isNew) + this.isInvalid(); + else this.check(); - else this.isInvalid(); } catch (err) { return this.$q.reject(err); } @@ -122,65 +273,42 @@ export default class Watcher extends Component { if (this.form) this.form.$setSubmitted(); - const isPost = (this.requestMethod() === 'post'); - if (!this.dataChanged() && !isPost) { - this.updateOriginalData(); + if (!this.dataChanged() && !this.isNew) { + this.snapshot(); return this.$q.resolve(); } - let changedData = isPost + let changedData = this.isNew ? this.data : getModifiedData(this.data, this.orgData); - let id = this.idField ? this.orgData[this.idField] : null; - // If watcher is associated to mgCrud if (this.save && this.save.accept) { - if (id) - changedData[this.idField] = id; - this.save.model = changedData; - return this.$q((resolve, reject) => { - this.save.accept().then( - json => this.writeData({data: json}, resolve), - reject - ); - }); + return this.save.accept() + .then(json => this.writeData({data: json})); } // When mgCrud is not used - if (id) { - return this.$q((resolve, reject) => { - this.$http.patch(`${this.url}/${id}`, changedData).then( - json => this.writeData(json, resolve), - reject - ); - }); - } + let req; - return this.$q((resolve, reject) => { - this.$http.post(this.url, changedData).then( - json => this.writeData(json, resolve), - reject - ); - }); - } - /** - * return the request method. - */ + if (this.deleted) + req = this.$http.delete(this.instanceUrl); + else if (this.isNew) + req = this.$http.post(this.url, changedData); + else + req = this.$http.patch(this.instanceUrl, changedData); - requestMethod() { - return this.$attrs.save && this.$attrs.save.toLowerCase(); + return req.then(res => this.writeData(res)); } /** * Checks if data is ready to send. */ check() { - if (this.form && this.form.$invalid) - throw new UserError('Some fields are invalid'); + this.isInvalid(); if (!this.dirty) throw new UserError('No changes to save'); } @@ -220,62 +348,82 @@ export default class Watcher extends Component { onConfirmResponse(response) { if (response === 'accept') { - if (this.data) - Object.assign(this.data, this.orgData); + this.reset(); this.$state.go(this.state); } else this.state = null; } - writeData(json, resolve) { - Object.assign(this.data, json.data); - this.updateOriginalData(); - resolve(json); - } - - updateOriginalData() { - this.orgData = this.copyInNewObject(this.data); - this.setPristine(); - } - + /** + * @deprecated Use reset() + */ loadOriginalData() { - const orgData = JSON.parse(JSON.stringify(this.orgData)); - this.data = Object.assign(this.data, orgData); - this.setPristine(); + this.reset(); } - copyInNewObject(data) { - let newCopy = {}; - if (data && typeof data === 'object') { - Object.keys(data).forEach( - key => { - let value = data[key]; - if (value instanceof Date) - newCopy[key] = new Date(value.getTime()); - else if (!isFullEmpty(value)) { - if (typeof value === 'object') - newCopy[key] = this.copyInNewObject(value); - else - newCopy[key] = value; - } - } - ); - } - - return newCopy; + /** + * @deprecated Use snapshot() + */ + updateOriginalData() { + this.snapshot(); } } -Watcher.$inject = ['$element', '$scope', '$state', '$stateParams', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q']; +Watcher.$inject = ['$element', '$scope']; + +function copyObject(data) { + let newCopy; + + if (data && typeof data === 'object') { + newCopy = {}; + Object.keys(data).forEach( + key => { + let value = data[key]; + if (value instanceof Date) + newCopy[key] = new Date(value.getTime()); + else if (!isFullEmpty(value)) { + if (typeof value === 'object') + newCopy[key] = copyObject(value); + else + newCopy[key] = value; + } + } + ); + } else + newCopy = data; + + return newCopy; +} + +function clearObject(obj) { + if (!obj) return; + for (let key in obj) { + if (obj.hasOwnProperty(key)) + delete obj[key]; + } +} + +function overwrite(obj, data) { + if (!obj) return; + clearObject(obj); + Object.assign(obj, data); +} ngModule.vnComponent('vnWatcher', { template: require('./watcher.html'), bindings: { url: '@?', idField: '@?', - data: '<', + idValue: ' { let $scope; @@ -9,10 +8,16 @@ describe('Component vnWatcher', () => { let controller; let $attrs; let $q; + let data; beforeEach(ngModule('vnCore')); beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$state_, _$q_) => { + data = { + id: 1, + foo: 'bar' + }; + $scope = $rootScope.$new(); $element = angular.element('
'); $state = _$state_; @@ -25,38 +30,49 @@ describe('Component vnWatcher', () => { })); describe('$onInit()', () => { - it('should call fetchData() if controllers get and url properties are defined', () => { - controller.get = () => {}; - controller.url = 'test.com'; - jest.spyOn(controller, 'fetchData').mockReturnThis(); + it('should set data empty by default', () => { controller.$onInit(); - expect(controller.fetchData).toHaveBeenCalledWith(); + expect(controller.data).toBeUndefined(); }); - it(`should throw an error if $onInit is called without url defined`, () => { - controller.get = () => {}; + it('should set new data when insert mode is enabled', () => { + controller.insertMode = true; + controller.data = data; - expect(function() { - controller.$onInit(); - }).toThrowError(/parameter/); + controller.$onInit(); + + expect(controller.orgData).toEqual(data); + expect(controller.orgData).toEqual(data); + expect(controller.isNew).toBeTruthy(); + }); + + it('should call backend and fetch data if url and idValue properties are defined', () => { + controller.url = 'Foos'; + controller.idValue = 1; + + $httpBackend.expectGET('Foos/1').respond(data); + controller.$onInit(); + $httpBackend.flush(); + + expect(controller.orgData).toEqual(data); + expect(controller.orgData).toEqual(data); + expect(controller.orgData).not.toBe(controller.data); }); }); - describe('fetchData()', () => { - it(`should perform a query then store the received data into controller.data and call updateOriginalData()`, () => { - jest.spyOn(controller, 'updateOriginalData'); - let json = {data: 'some data'}; - controller.data = [1]; - controller.idField = 0; - controller.url = 'test.com'; - $httpBackend.whenGET('test.com/1').respond(json); - $httpBackend.expectGET('test.com/1'); - controller.fetchData(); + describe('fetch()', () => { + it(`should perform a query then store the received data into data property and make an snapshot into orgData`, () => { + controller.url = 'Bars'; + controller.idValue = 1; + + $httpBackend.expectGET('Bars/1').respond(data); + controller.$onInit(); $httpBackend.flush(); - expect(controller.data).toEqual({data: 'some data'}); - expect(controller.updateOriginalData).toHaveBeenCalledWith(); + expect(controller.orgData).toEqual(data); + expect(controller.orgData).toEqual(data); + expect(controller.orgData).not.toBe(controller.data); }); }); @@ -95,14 +111,6 @@ describe('Component vnWatcher', () => { controller.check(); }).toThrowError(); }); - - it(`should throw error if controller.dirty is true`, () => { - controller.form = {$invalid: true}; - - expect(function() { - controller.check(); - }).toThrowError(); - }); }); describe('realSubmit()', () => { @@ -130,59 +138,60 @@ describe('Component vnWatcher', () => { }); }); - describe('when id is defined', () => { - it(`should perform a query then call controller.writeData()`, done => { - controller.dataChanged = () => { - return true; - }; - controller.data = {id: 2}; - controller.orgData = {id: 1}; - let changedData = getModifiedData(controller.data, controller.orgData); - controller.idField = 'id'; - controller.url = 'test.com'; - let json = {data: 'some data'}; - jest.spyOn(controller, 'writeData'); - $httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json); - $httpBackend.expectPATCH(`${controller.url}/1`); - controller.realSubmit() - .then(() => { - expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); - done(); - }).catch(done.fail); + describe('should perform a PATCH query and save the data', () => { + it(`should perform a query then call controller.writeData()`, () => { + controller.url = 'Foos'; + controller.data = data; + + const changedData = {baz: 'value'}; + Object.assign(controller.data, changedData); + + $httpBackend.expectPATCH('Foos/1', changedData).respond({newProp: 'some'}); + controller.realSubmit(); $httpBackend.flush(); + + expect(controller.data).toEqual(Object.assign({}, data, changedData)); }); }); - it(`should perform a POST query then call controller.writeData()`, done => { - controller.dataChanged = () => { - return true; - }; - controller.data = {id: 2}; - controller.orgData = {id: 1}; - controller.url = 'test.com'; - let json = {data: 'some data'}; - jest.spyOn(controller, 'writeData'); - $httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json); - $httpBackend.expectPOST(`${controller.url}`, controller.data); - controller.realSubmit() - .then(() => { - expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); - done(); - }).catch(done.fail); + it(`should perform a POST query and save the data`, () => { + controller.insertMode = true; + controller.url = 'Foos'; + controller.data = data; + + const changedData = {baz: 'value'}; + Object.assign(controller.data, changedData); + + $httpBackend.expectPOST('Foos', controller.data).respond({newProp: 'some'}); + controller.realSubmit(); $httpBackend.flush(); + + expect(controller.data).toEqual(Object.assign({}, data, changedData)); + }); + + describe('should perform a DELETE query and save empty data', () => { + it(`should perform a query then call controller.writeData()`, () => { + controller.url = 'Foos'; + controller.data = data; + controller.delete(); + + $httpBackend.expectDELETE('Foos/1').respond(); + controller.realSubmit(); + $httpBackend.flush(); + + expect(controller.data).toBeNull(); + }); }); }); describe('writeData()', () => { - it(`should call Object.asssign() function over controllers.data with json.data, then call updateOriginalData function and finally call resolve() function`, () => { - jest.spyOn(controller, 'updateOriginalData'); - controller.data = {}; - let json = {data: 'some data'}; - let resolve = jasmine.createSpy('resolve'); - controller.writeData(json, resolve); + it(`should save data into orgData`, () => { + controller.data = data; + Object.assign(controller.data, {baz: 'value'}); - expect(controller.updateOriginalData).toHaveBeenCalledWith(); - expect(resolve).toHaveBeenCalledWith(json); + controller.writeData({}); + + expect(controller.data).toEqual(controller.orgData); }); }); @@ -224,37 +233,38 @@ describe('Component vnWatcher', () => { describe(`onConfirmResponse()`, () => { describe(`when response is accept`, () => { - it(`should call Object.assing on controlle.data with controller.orgData then call go() on state`, () => { - let response = 'accept'; - controller.data = {}; - controller.orgData = {name: 'Batman'}; + it(`should reset data them go to state`, () => { + let data = {key: 'value'}; + controller.data = data; + data.foo = 'bar'; controller.$state = {go: jasmine.createSpy('go')}; - controller.state = 'Batman'; - controller.onConfirmResponse(response); + controller.state = 'foo.bar'; + controller.onConfirmResponse('accept'); - expect(controller.data).toEqual(controller.orgData); + expect(controller.data).toEqual({key: 'value'}); expect(controller.$state.go).toHaveBeenCalledWith(controller.state); }); }); describe(`when response is not accept`, () => { it(`should set controller.state to null`, () => { - let response = 'anything but accept'; controller.state = 'Batman'; - controller.onConfirmResponse(response); + controller.onConfirmResponse('cancel'); expect(controller.state).toBeFalsy(); }); }); }); - describe(`loadOriginalData()`, () => { - it(`should iterate over the current data object, delete all properties then assign the ones from original data`, () => { - controller.data = {name: 'Bruce'}; - controller.orgData = {name: 'Batman'}; - controller.loadOriginalData(); + describe(`reset()`, () => { + it(`should reset data as it was before changing it`, () => { + let data = {key: 'value'}; - expect(controller.data).toEqual(controller.orgData); + controller.data = data; + data.foo = 'bar'; + controller.reset(); + + expect(data).toEqual({key: 'value'}); }); }); }); diff --git a/front/core/directives/rule.js b/front/core/directives/rule.js index 85b6041f8..f65efe176 100644 --- a/front/core/directives/rule.js +++ b/front/core/directives/rule.js @@ -65,7 +65,7 @@ export function directive($translate, $window) { }; if (form) - $scope.$watch(form.$submitted, refreshError); + $scope.$watch(() => form.$submitted, refreshError); } } ngModule.directive('rule', directive); diff --git a/front/core/styles/effects.scss b/front/core/styles/effects.scss index 205a23bd2..38499f52f 100644 --- a/front/core/styles/effects.scss +++ b/front/core/styles/effects.scss @@ -25,4 +25,10 @@ %active { background-color: $color-active; color: $color-active-font; + + &:hover, + &:focus { + background-color: $color-active; + color: $color-active-font; + } } diff --git a/front/core/styles/icons/MaterialIcons-Regular.woff2 b/front/core/styles/icons/MaterialIcons-Regular.woff2 index 9fa211252080046a23b2449dbdced6abc2b0bb34..2b86ebfe6826a4ce14ed3682edb20d4d12a88978 100644 GIT binary patch literal 82512 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 QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/front/module-import.js b/front/module-import.js index dd1692c18..baebe30eb 100755 --- a/front/module-import.js +++ b/front/module-import.js @@ -18,5 +18,6 @@ export default function moduleImport(moduleName) { case 'invoiceOut' : return import('invoiceOut/front'); case 'route' : return import('route/front'); case 'entry' : return import('entry/front'); + case 'account' : return import('account/front'); } } diff --git a/front/salix/components/descriptor/index.html b/front/salix/components/descriptor/index.html index 366bfab5d..ed2305f13 100644 --- a/front/salix/components/descriptor/index.html +++ b/front/salix/components/descriptor/index.html @@ -8,13 +8,13 @@

+ ui-sref="{{::$ctrl.summaryState}}({id: $ctrl.descriptor.id})">
  • - - diff --git a/front/salix/components/left-menu/left-menu.js b/front/salix/components/left-menu/left-menu.js index 5f047060a..da545b291 100644 --- a/front/salix/components/left-menu/left-menu.js +++ b/front/salix/components/left-menu/left-menu.js @@ -32,73 +32,86 @@ export default class LeftMenu { let moduleIndex = this.$state.current.data.moduleIndex; let moduleFile = window.routes[moduleIndex] || []; let menu = moduleFile.menus && moduleFile.menus[this.source] || []; - let items = []; - let addItem = (items, item) => { - let state = states[item.state]; - if (state) { - state = state.self; - let acl = state.data.acl; + let cloneItems = (items, parent) => { + let myItems = []; - if (acl && !this.aclService.hasAny(acl)) - return; - } else if (!item.external) { - console.warn('wrong left-menu definition'); - return; + for (let item of items) { + let state = states[item.state]; + if (state) { + state = state.self; + let acl = state.data.acl; + + if (acl && !this.aclService.hasAny(acl)) + continue; + } + + let myItem = { + icon: item.icon, + description: item.description || state.description, + state: item.state, + external: item.external, + url: item.url, + parent + }; + + if (item.childs) { + let myChilds = cloneItems(item.childs, myItem); + + if (myChilds.length > 0) { + myItem.childs = myChilds; + myItems.push(myItem); + } + } else + myItems.push(myItem); } - items.push({ - icon: item.icon, - description: item.description || state.description, - state: item.state, - external: item.external, - url: item.url - }); + return myItems; }; - for (let item of menu) { - if (item.state || item.external) - addItem(items, item); - else { - let childs = []; - - for (let child of item.childs) - addItem(childs, child); - - if (childs.length > 0) { - items.push({ - icon: item.icon, - description: item.description, - childs: childs - }); - } - } - } - - return items; + return cloneItems(menu); } activateItem() { - let myState = this.$state.current.name - .split('.') - .slice(0, this._depth) - .join('.'); - let re = new RegExp(`^${myState}(\\..*)?$`); + if (!this.items) return; + let currentState = this.$state.current.name; + let maxSpecificity = 0; + let selectedItem; - if (this.items) { - // Check items matching current path - for (let item of this.items) { - item.active = re.test(item.state); + function isParentState(state, currentState) { + if (!state) return 0; + let match = state.match(/^(.*)\.index$/); + if (match) state = match[1]; - if (item.childs) { - for (let child of item.childs) { - child.active = re.test(child.state); - if (child.active) - item.active = child.active; - } + let isParent = + currentState.startsWith(`${state}.`) || + currentState === state; + + return isParent + ? (state.match(/\./g) || []).length + 1 + : 0; + } + + function selectItem(items) { + if (!items) return; + for (let item of items) { + item.active = false; + let specificity = isParentState(item.state, currentState); + if (specificity > maxSpecificity) { + selectedItem = item; + maxSpecificity = specificity; } + selectItem(item.childs); } } + + // Check items matching current path + selectItem(this.items); + + while (selectedItem) { + selectedItem.active = true; + selectedItem = selectedItem.parent; + } } setActive(item) { diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml index 287f840a5..75c65ef64 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -44,6 +44,7 @@ Routes: Rutas Locator: Localizador Invoices out: Facturas emitidas Entries: Entradas +Users: Usuarios # Common diff --git a/loopback/locale/es.json b/loopback/locale/es.json index d28cceb41..285617a77 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -134,5 +134,12 @@ "This ticket is deleted": "Este ticket está eliminado", "A travel with this data already exists": "Ya existe un travel con estos datos", "This thermograph id already exists": "La id del termógrafo ya existe", - "Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante" + "Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante", + "ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED", + "Invalid password": "Invalid password", + "Password does not meet requirements": "Password does not meet requirements", + "Role already assigned": "Role already assigned", + "Invalid role name": "Invalid role name", + "Role name must be written in camelCase": "Role name must be written in camelCase", + "can't be set": "can't be set" } \ No newline at end of file diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json new file mode 100644 index 000000000..48d7c427d --- /dev/null +++ b/modules/account/back/model-config.json @@ -0,0 +1,24 @@ +{ + "MailAlias": { + "dataSource": "vn" + }, + "MailAliasAccount": { + "dataSource": "vn" + }, + "MailForward": { + "dataSource": "vn" + }, + "RoleInherit": { + "dataSource": "vn" + }, + "RoleRole": { + "dataSource": "vn" + }, + "UserAccount": { + "dataSource": "vn" + }, + "UserPassword": { + "dataSource": "vn" + } + +} \ No newline at end of file diff --git a/modules/account/back/models/mail-alias-account.json b/modules/account/back/models/mail-alias-account.json new file mode 100644 index 000000000..114d401e0 --- /dev/null +++ b/modules/account/back/models/mail-alias-account.json @@ -0,0 +1,27 @@ +{ + "name": "MailAliasAccount", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.mailAliasAccount" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + } + }, + "relations": { + "alias": { + "type": "belongsTo", + "model": "MailAlias", + "foreignKey": "mailAlias" + }, + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "account" + } + } +} diff --git a/modules/account/back/models/mail-alias.json b/modules/account/back/models/mail-alias.json new file mode 100644 index 000000000..a5970bd3f --- /dev/null +++ b/modules/account/back/models/mail-alias.json @@ -0,0 +1,33 @@ +{ + "name": "MailAlias", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.mailAlias" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "alias": { + "type": "string", + "required": true + }, + "description": { + "type": "string" + }, + "isPublic": { + "type": "boolean" + } + }, + "relations": { + "accounts": { + "type": "hasMany", + "model": "MailAliasAccount", + "foreignKey": "mailAlias", + "property": "id" + } + } +} diff --git a/modules/account/back/models/mail-forward.json b/modules/account/back/models/mail-forward.json new file mode 100644 index 000000000..a3e0eafd9 --- /dev/null +++ b/modules/account/back/models/mail-forward.json @@ -0,0 +1,25 @@ +{ + "name": "MailForward", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.mailForward" + } + }, + "properties": { + "account": { + "id": true + }, + "forwardTo": { + "type": "string", + "required": true + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "account" + } + } +} diff --git a/modules/account/back/models/role-inherit.json b/modules/account/back/models/role-inherit.json new file mode 100644 index 000000000..4b69ffdc2 --- /dev/null +++ b/modules/account/back/models/role-inherit.json @@ -0,0 +1,27 @@ +{ + "name": "RoleInherit", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.roleInherit" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + } + }, + "relations": { + "owner": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "role" + }, + "inherits": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "inheritsFrom" + } + } +} diff --git a/modules/account/back/models/role-role.js b/modules/account/back/models/role-role.js new file mode 100644 index 000000000..e2e860187 --- /dev/null +++ b/modules/account/back/models/role-role.js @@ -0,0 +1,20 @@ +const app = require('vn-loopback/server/server'); + +module.exports = Self => { + app.on('started', function() { + let hooks = ['after save', 'after delete']; + for (let hook of hooks) { + app.models.RoleInherit.observe(hook, async() => { + try { + await Self.rawSql(` + CREATE EVENT account.role_sync + ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND + DO CALL role_sync; + `); + } catch (err) { + if (err.code != 'ER_EVENT_ALREADY_EXISTS') throw err; + } + }); + } + }); +}; diff --git a/modules/account/back/models/role-role.json b/modules/account/back/models/role-role.json new file mode 100644 index 000000000..f8f16e9e7 --- /dev/null +++ b/modules/account/back/models/role-role.json @@ -0,0 +1,26 @@ +{ + "name": "RoleRole", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.roleRole" + } + }, + "properties": { + "role": { + "id": true + } + }, + "relations": { + "owner": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "role" + }, + "inherits": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "inheritsFrom" + } + } +} diff --git a/modules/account/back/models/user-account.json b/modules/account/back/models/user-account.json new file mode 100644 index 000000000..fc0526388 --- /dev/null +++ b/modules/account/back/models/user-account.json @@ -0,0 +1,26 @@ +{ + "name": "UserAccount", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.account" + } + }, + "properties": { + "id": { + "id": true + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "id" + }, + "aliases": { + "type": "hasMany", + "model": "MailAliasAccount", + "foreignKey": "account" + } + } +} diff --git a/modules/account/back/models/user-password.json b/modules/account/back/models/user-password.json new file mode 100644 index 000000000..1b7e49edd --- /dev/null +++ b/modules/account/back/models/user-password.json @@ -0,0 +1,34 @@ +{ + "name": "UserPassword", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.userPassword" + } + }, + "properties": { + "id": { + "id": true + }, + "length": { + "type": "number", + "required": true + }, + "nAlpha": { + "type": "number", + "required": true + }, + "nUpper": { + "type": "number", + "required": true + }, + "nDigits": { + "type": "number", + "required": true + }, + "nPunct": { + "type": "number", + "required": true + } + } +} diff --git a/modules/account/front/acl/create/index.html b/modules/account/front/acl/create/index.html new file mode 100644 index 000000000..96fe5abad --- /dev/null +++ b/modules/account/front/acl/create/index.html @@ -0,0 +1,65 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/modules/account/front/acl/create/index.js b/modules/account/front/acl/create/index.js new file mode 100644 index 000000000..fea71991f --- /dev/null +++ b/modules/account/front/acl/create/index.js @@ -0,0 +1,33 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor(...args) { + super(...args); + this.accessTypes = [ + {name: '*'}, + {name: 'READ'}, + {name: 'WRITE'} + ]; + this.permissions = [ + {name: 'ALLOW'}, + {name: 'DENY'} + ]; + + this.models = []; + for (let model in window.validations) + this.models.push({name: model}); + + this.acl = { + property: '*', + principalType: 'ROLE', + accessType: 'READ', + permission: 'ALLOW' + }; + } +} + +ngModule.component('vnAclCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/acl/index.js b/modules/account/front/acl/index.js new file mode 100644 index 000000000..8393859a5 --- /dev/null +++ b/modules/account/front/acl/index.js @@ -0,0 +1,4 @@ +import './main'; +import './index/'; +import './create'; +import './search-panel'; diff --git a/modules/account/front/acl/index/index.html b/modules/account/front/acl/index/index.html new file mode 100644 index 000000000..af06ec481 --- /dev/null +++ b/modules/account/front/acl/index/index.html @@ -0,0 +1,51 @@ + + + + + +
    + +
    {{::row.model}}.{{::row.property}}
    + + + + + + +
    + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/acl/index/index.js b/modules/account/front/acl/index/index.js new file mode 100644 index 000000000..a2aec534a --- /dev/null +++ b/modules/account/front/acl/index/index.js @@ -0,0 +1,15 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onDelete(row) { + return this.$http.delete(`ACLs/${row.id}`) + .then(() => this.$.model.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('ACL removed'))); + } +} + +ngModule.component('vnAclIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/acl/index/locale/es.yml b/modules/account/front/acl/index/locale/es.yml new file mode 100644 index 000000000..8024f804c --- /dev/null +++ b/modules/account/front/acl/index/locale/es.yml @@ -0,0 +1,4 @@ +New ACL: Nuevo ACL +Edit ACL: Editar ACL +ACL will be removed: El ACL será eliminado +ACL removed: ACL eliminado diff --git a/modules/account/front/acl/locale/es.yml b/modules/account/front/acl/locale/es.yml new file mode 100644 index 000000000..ff6a1b41c --- /dev/null +++ b/modules/account/front/acl/locale/es.yml @@ -0,0 +1,4 @@ +Model: Modelo +Property: Propiedad +Access type: Tipo de acceso +Permission: Permiso \ No newline at end of file diff --git a/modules/account/front/acl/main/index.html b/modules/account/front/acl/main/index.html new file mode 100644 index 000000000..7767768d9 --- /dev/null +++ b/modules/account/front/acl/main/index.html @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/acl/main/index.js b/modules/account/front/acl/main/index.js new file mode 100644 index 000000000..a91a71cb7 --- /dev/null +++ b/modules/account/front/acl/main/index.js @@ -0,0 +1,18 @@ +import ngModule from '../../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class ACL extends ModuleMain { + exprBuilder(param, value) { + switch (param) { + case 'search': + return {model: {like: `%${value}%`}}; + default: + return {[param]: value}; + } + } +} + +ngModule.vnComponent('vnAclComponent', { + controller: ACL, + template: require('./index.html') +}); diff --git a/modules/account/front/acl/search-panel/index.html b/modules/account/front/acl/search-panel/index.html new file mode 100644 index 000000000..b83b9c255 --- /dev/null +++ b/modules/account/front/acl/search-panel/index.html @@ -0,0 +1,39 @@ +
    +
    + + + + + + + + + + + + + + +
    +
    \ No newline at end of file diff --git a/modules/account/front/acl/search-panel/index.js b/modules/account/front/acl/search-panel/index.js new file mode 100644 index 000000000..4f571059e --- /dev/null +++ b/modules/account/front/acl/search-panel/index.js @@ -0,0 +1,26 @@ +import ngModule from '../../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +export default class Controller extends SearchPanel { + constructor(...args) { + super(...args); + this.accessTypes = [ + {name: '*'}, + {name: 'READ'}, + {name: 'WRITE'} + ]; + this.permissions = [ + {name: 'ALLOW'}, + {name: 'DENY'} + ]; + + this.models = []; + for (let model in window.validations) + this.models.push({name: model}); + } +} + +ngModule.component('vnAclSearchPanel', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/alias/basic-data/index.html b/modules/account/front/alias/basic-data/index.html new file mode 100644 index 000000000..ede77f929 --- /dev/null +++ b/modules/account/front/alias/basic-data/index.html @@ -0,0 +1,44 @@ + + +
    + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/modules/account/front/alias/basic-data/index.js b/modules/account/front/alias/basic-data/index.js new file mode 100644 index 000000000..b7c2db089 --- /dev/null +++ b/modules/account/front/alias/basic-data/index.js @@ -0,0 +1,12 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section {} + +ngModule.component('vnAliasBasicData', { + template: require('./index.html'), + controller: Controller, + bindings: { + alias: '<' + } +}); diff --git a/modules/account/front/alias/card/index.html b/modules/account/front/alias/card/index.html new file mode 100644 index 000000000..712147a24 --- /dev/null +++ b/modules/account/front/alias/card/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/account/front/alias/card/index.js b/modules/account/front/alias/card/index.js new file mode 100644 index 000000000..fd1a18f6a --- /dev/null +++ b/modules/account/front/alias/card/index.js @@ -0,0 +1,14 @@ +import ngModule from '../../module'; +import ModuleCard from 'salix/components/module-card'; + +class Controller extends ModuleCard { + reload() { + this.$http.get(`MailAliases/${this.$params.id}`) + .then(res => this.alias = res.data); + } +} + +ngModule.vnComponent('vnAliasCard', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/alias/create/index.html b/modules/account/front/alias/create/index.html new file mode 100644 index 000000000..dee59d26e --- /dev/null +++ b/modules/account/front/alias/create/index.html @@ -0,0 +1,33 @@ + + +
    + + + + + + + + + + +
    diff --git a/modules/account/front/alias/create/index.js b/modules/account/front/alias/create/index.js new file mode 100644 index 000000000..c058c3adf --- /dev/null +++ b/modules/account/front/alias/create/index.js @@ -0,0 +1,15 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + return this.$.watcher.submit().then(res => + this.$state.go('account.alias.card.basicData', {id: res.data.id}) + ); + } +} + +ngModule.component('vnAliasCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/alias/descriptor/index.html b/modules/account/front/alias/descriptor/index.html new file mode 100644 index 000000000..71b98c6a3 --- /dev/null +++ b/modules/account/front/alias/descriptor/index.html @@ -0,0 +1,27 @@ + + + + Delete + + + +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/modules/account/front/alias/descriptor/index.js b/modules/account/front/alias/descriptor/index.js new file mode 100644 index 000000000..a21baae5a --- /dev/null +++ b/modules/account/front/alias/descriptor/index.js @@ -0,0 +1,26 @@ +import ngModule from '../../module'; +import Descriptor from 'salix/components/descriptor'; + +class Controller extends Descriptor { + get alias() { + return this.entity; + } + + set alias(value) { + this.entity = value; + } + + onDelete() { + return this.$http.delete(`MailAliases/${this.id}`) + .then(() => this.$state.go('account.alias')) + .then(() => this.vnApp.showSuccess(this.$t('Alias removed'))); + } +} + +ngModule.component('vnAliasDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + alias: '<' + } +}); diff --git a/modules/account/front/alias/descriptor/locale/es.yml b/modules/account/front/alias/descriptor/locale/es.yml new file mode 100644 index 000000000..9c6fa0e73 --- /dev/null +++ b/modules/account/front/alias/descriptor/locale/es.yml @@ -0,0 +1,2 @@ +Alias will be removed: El alias será eliminado +Alias removed: Alias eliminado \ No newline at end of file diff --git a/modules/account/front/alias/index.js b/modules/account/front/alias/index.js new file mode 100644 index 000000000..8eed3a3d3 --- /dev/null +++ b/modules/account/front/alias/index.js @@ -0,0 +1,9 @@ +import './main'; +import './index/'; +import './create'; +import './summary'; +import './card'; +import './descriptor'; +import './create'; +import './basic-data'; +import './users'; diff --git a/modules/account/front/alias/index/index.html b/modules/account/front/alias/index/index.html new file mode 100644 index 000000000..d140973ba --- /dev/null +++ b/modules/account/front/alias/index/index.html @@ -0,0 +1,39 @@ + + + + + + + +
    {{::alias.alias}}
    +
    {{::alias.description}}
    +
    + + + + +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/modules/account/front/alias/index/index.js b/modules/account/front/alias/index/index.js new file mode 100644 index 000000000..44e146fb4 --- /dev/null +++ b/modules/account/front/alias/index/index.js @@ -0,0 +1,14 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + preview(alias) { + this.selectedAlias = alias; + this.$.summary.show(); + } +} + +ngModule.component('vnAliasIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/alias/index/locale/es.yml b/modules/account/front/alias/index/locale/es.yml new file mode 100644 index 000000000..4df41c0be --- /dev/null +++ b/modules/account/front/alias/index/locale/es.yml @@ -0,0 +1,2 @@ +New alias: Nuevo alias +View alias: Ver alias \ No newline at end of file diff --git a/modules/account/front/alias/locale/es.yml b/modules/account/front/alias/locale/es.yml new file mode 100644 index 000000000..ecc856fcf --- /dev/null +++ b/modules/account/front/alias/locale/es.yml @@ -0,0 +1 @@ +Public: Público \ No newline at end of file diff --git a/modules/account/front/alias/main/index.html b/modules/account/front/alias/main/index.html new file mode 100644 index 000000000..43f6e2f51 --- /dev/null +++ b/modules/account/front/alias/main/index.html @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/alias/main/index.js b/modules/account/front/alias/main/index.js new file mode 100644 index 000000000..21eed3d85 --- /dev/null +++ b/modules/account/front/alias/main/index.js @@ -0,0 +1,18 @@ +import ngModule from '../../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class Alias extends ModuleMain { + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {alias: {like: `%${value}%`}}; + } + } +} + +ngModule.vnComponent('vnAlias', { + controller: Alias, + template: require('./index.html') +}); diff --git a/modules/account/front/alias/summary/index.html b/modules/account/front/alias/summary/index.html new file mode 100644 index 000000000..52ee2813d --- /dev/null +++ b/modules/account/front/alias/summary/index.html @@ -0,0 +1,16 @@ + +
    {{summary.alias}}
    + + +

    Basic data

    + + + + +
    +
    +
    \ No newline at end of file diff --git a/modules/account/front/alias/summary/index.js b/modules/account/front/alias/summary/index.js new file mode 100644 index 000000000..21bc8d9ba --- /dev/null +++ b/modules/account/front/alias/summary/index.js @@ -0,0 +1,25 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; + +class Controller extends Component { + set alias(value) { + this._alias = value; + this.$.summary = null; + if (!value) return; + + this.$http.get(`MailAliases/${value.id}`) + .then(res => this.$.summary = res.data); + } + + get alias() { + return this._alias; + } +} + +ngModule.component('vnAliasSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + alias: '<' + } +}); diff --git a/modules/account/front/alias/users/index.html b/modules/account/front/alias/users/index.html new file mode 100644 index 000000000..048a702ea --- /dev/null +++ b/modules/account/front/alias/users/index.html @@ -0,0 +1,26 @@ + + + + + + {{::row.user.name}} + + + + + + + + + + + diff --git a/modules/account/front/alias/users/index.js b/modules/account/front/alias/users/index.js new file mode 100644 index 000000000..b2446d71b --- /dev/null +++ b/modules/account/front/alias/users/index.js @@ -0,0 +1,31 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + $onInit() { + let filter = { + include: { + relation: 'user', + scope: { + fields: ['id', 'name'] + } + } + }; + this.$http.get(`MailAliases/${this.$params.id}/accounts`, {filter}) + .then(res => this.$.data = res.data); + } + + onRemove(row) { + return this.$http.delete(`MailAliases/${this.$params.id}/accounts/${row.id}`) + .then(() => { + let index = this.$.data.indexOf(row); + if (index !== -1) this.$.data.splice(index, 1); + this.vnApp.showSuccess(this.$t('User removed')); + }); + } +} + +ngModule.component('vnAliasUsers', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/alias/users/index.spec.js b/modules/account/front/alias/users/index.spec.js new file mode 100644 index 000000000..d618f1de1 --- /dev/null +++ b/modules/account/front/alias/users/index.spec.js @@ -0,0 +1,42 @@ +import './index'; + +describe('component vnAliasUsers', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnAliasUsers', {$element: null}); + controller.$params.id = 1; + })); + + describe('$onInit()', () => { + it('should delete entity and go to index', () => { + $httpBackend.expectGET('MailAliases/1/accounts').respond('foo'); + controller.$onInit(); + $httpBackend.flush(); + + expect(controller.$.data).toBe('foo'); + }); + }); + + describe('onRemove()', () => { + it('should call backend method to change role', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.$.data = [ + {id: 1, alias: 'foo'}, + {id: 2, alias: 'bar'} + ]; + + $httpBackend.expectDELETE('MailAliases/1/accounts/1').respond(); + controller.onRemove(controller.$.data[0]); + $httpBackend.flush(); + + expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/account/front/alias/users/locale/es.yml b/modules/account/front/alias/users/locale/es.yml new file mode 100644 index 000000000..dc24eb318 --- /dev/null +++ b/modules/account/front/alias/users/locale/es.yml @@ -0,0 +1,2 @@ +User will be removed from alias: El usuario será borrado del alias +User removed: Usuario borrado \ No newline at end of file diff --git a/modules/account/front/aliases/index.html b/modules/account/front/aliases/index.html new file mode 100644 index 000000000..9f4ba857f --- /dev/null +++ b/modules/account/front/aliases/index.html @@ -0,0 +1,64 @@ +
    + + + + + +
    + {{::row.alias.alias}} +
    +
    + {{::row.alias.description}} +
    +
    + + + + +
    +
    + +
    +
    + + + + + + + + + + + + + + +
    +
    + Account not enabled +
    diff --git a/modules/account/front/aliases/index.js b/modules/account/front/aliases/index.js new file mode 100644 index 000000000..0fc806a71 --- /dev/null +++ b/modules/account/front/aliases/index.js @@ -0,0 +1,51 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + $onInit() { + this.refresh(); + } + + refresh() { + let filter = { + where: {account: this.$params.id}, + include: { + relation: 'alias', + scope: { + fields: ['id', 'alias', 'description'] + } + } + }; + return this.$http.get(`MailAliasAccounts`, {filter}) + .then(res => this.$.data = res.data); + } + + onAddClick() { + this.addData = {account: this.$params.id}; + this.$.dialog.show(); + } + + onAddSave() { + return this.$http.post(`MailAliasAccounts`, this.addData) + .then(() => this.refresh()) + .then(() => this.vnApp.showSuccess( + this.$t('Subscribed to alias!')) + ); + } + + onRemove(row) { + return this.$http.delete(`MailAliasAccounts/${row.id}`) + .then(() => { + this.$.data.splice(this.$.data.indexOf(row), 1); + this.vnApp.showSuccess(this.$t('Unsubscribed from alias!')); + }); + } +} + +ngModule.component('vnUserAliases', { + template: require('./index.html'), + controller: Controller, + require: { + card: '^vnUserCard' + } +}); diff --git a/modules/account/front/aliases/index.spec.js b/modules/account/front/aliases/index.spec.js new file mode 100644 index 000000000..466f1e1e9 --- /dev/null +++ b/modules/account/front/aliases/index.spec.js @@ -0,0 +1,53 @@ +import './index'; + +describe('component vnUserAliases', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnUserAliases', {$element: null}); + jest.spyOn(controller.vnApp, 'showSuccess'); + })); + + describe('refresh()', () => { + it('should refresh the controller data', () => { + $httpBackend.expectGET('MailAliasAccounts').respond('foo'); + controller.refresh(); + $httpBackend.flush(); + + expect(controller.$.data).toBe('foo'); + }); + }); + + describe('onAddSave()', () => { + it('should add the new row', () => { + controller.addData = {account: 1}; + + $httpBackend.expectPOST('MailAliasAccounts').respond(); + $httpBackend.expectGET('MailAliasAccounts').respond('foo'); + controller.onAddSave(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('onRemove()', () => { + it('shoud remove the passed row remote and locally', () => { + controller.$.data = [ + {id: 1, alias: 'foo'}, + {id: 2, alias: 'bar'} + ]; + + $httpBackend.expectDELETE('MailAliasAccounts/1').respond(); + controller.onRemove(controller.$.data[0]); + $httpBackend.flush(); + + expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/account/front/aliases/locale/es.yml b/modules/account/front/aliases/locale/es.yml new file mode 100644 index 000000000..4d1ad76a7 --- /dev/null +++ b/modules/account/front/aliases/locale/es.yml @@ -0,0 +1,3 @@ +Unsubscribe: Desuscribir +Subscribed to alias!: ¡Suscrito al alias! +Unsubscribed from alias!: ¡Desuscrito del alias! \ No newline at end of file diff --git a/modules/account/front/basic-data/index.html b/modules/account/front/basic-data/index.html new file mode 100644 index 000000000..ca87d14b4 --- /dev/null +++ b/modules/account/front/basic-data/index.html @@ -0,0 +1,52 @@ + + +
    + + + + + + + + + + + + + + + + + + +
    diff --git a/modules/account/front/basic-data/index.js b/modules/account/front/basic-data/index.js new file mode 100644 index 000000000..243a46068 --- /dev/null +++ b/modules/account/front/basic-data/index.js @@ -0,0 +1,20 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + this.$.watcher.submit() + .then(() => this.card.reload()); + } +} + +ngModule.component('vnUserBasicData', { + template: require('./index.html'), + controller: Controller, + require: { + card: '^vnUserCard' + }, + bindings: { + user: '<' + } +}); diff --git a/modules/account/front/card/index.html b/modules/account/front/card/index.html new file mode 100644 index 000000000..cba6b93c6 --- /dev/null +++ b/modules/account/front/card/index.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/modules/account/front/card/index.js b/modules/account/front/card/index.js new file mode 100644 index 000000000..5266592f3 --- /dev/null +++ b/modules/account/front/card/index.js @@ -0,0 +1,28 @@ +import ngModule from '../module'; +import ModuleCard from 'salix/components/module-card'; +import './style.scss'; + +class Controller extends ModuleCard { + reload() { + const filter = { + include: { + relation: 'role', + scope: { + fields: ['id', 'name'] + } + } + }; + + return Promise.all([ + this.$http.get(`Accounts/${this.$params.id}`, {filter}) + .then(res => this.user = res.data), + this.$http.get(`UserAccounts/${this.$params.id}/exists`) + .then(res => this.hasAccount = res.data.exists) + ]); + } +} + +ngModule.vnComponent('vnUserCard', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/card/index.spec.js b/modules/account/front/card/index.spec.js new file mode 100644 index 000000000..cd28c458a --- /dev/null +++ b/modules/account/front/card/index.spec.js @@ -0,0 +1,27 @@ +import './index'; + +describe('component vnUserCard', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnUserCard', {$element: null}); + })); + + describe('reload()', () => { + it('should reload the controller data', () => { + controller.$params.id = 1; + + $httpBackend.expectGET('Accounts/1').respond('foo'); + $httpBackend.expectGET('UserAccounts/1/exists').respond({exists: true}); + controller.reload(); + $httpBackend.flush(); + + expect(controller.user).toBe('foo'); + expect(controller.hasAccount).toBeTruthy(); + }); + }); +}); diff --git a/modules/account/front/card/style.scss b/modules/account/front/card/style.scss new file mode 100644 index 000000000..4d9d108a0 --- /dev/null +++ b/modules/account/front/card/style.scss @@ -0,0 +1,10 @@ +@import "variables"; + +.bg-title { + display: block; + text-align: center; + padding: 24px; + box-sizing: border-box; + color: $color-font-secondary; + font-size: 1.375rem; +} diff --git a/modules/account/front/connections/index.html b/modules/account/front/connections/index.html new file mode 100644 index 000000000..419a9744a --- /dev/null +++ b/modules/account/front/connections/index.html @@ -0,0 +1,45 @@ + + + + + + + +
    {{::row.user.username}}
    +
    {{::row.created | date:'dd/MM HH:mm'}}
    +
    + + + + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/modules/account/front/connections/index.js b/modules/account/front/connections/index.js new file mode 100644 index 000000000..c4ddd5615 --- /dev/null +++ b/modules/account/front/connections/index.js @@ -0,0 +1,29 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor(...args) { + super(...args); + this.filter = { + fields: ['id', 'created', 'userId'], + include: { + relation: 'user', + scope: { + fields: ['username'] + } + }, + order: 'created DESC' + }; + } + + onDisconnect(row) { + return this.$http.delete(`AccessTokens/${row.id}`) + .then(() => this.$.model.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('Session killed'))); + } +} + +ngModule.component('vnConnections', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/connections/locale/es.yml b/modules/account/front/connections/locale/es.yml new file mode 100644 index 000000000..41ef18b45 --- /dev/null +++ b/modules/account/front/connections/locale/es.yml @@ -0,0 +1,5 @@ +Go to user: Ir al usuario +Refresh: Actualizar +Session will be killed: Se va a matar la sesión +Kill session: Matar sesión +Session killed: Sesión matada \ No newline at end of file diff --git a/modules/account/front/create/index.html b/modules/account/front/create/index.html new file mode 100644 index 000000000..407ac0e3c --- /dev/null +++ b/modules/account/front/create/index.html @@ -0,0 +1,53 @@ + + +
    + + + + + + + + + + + + + + + + + + +
    diff --git a/modules/account/front/create/index.js b/modules/account/front/create/index.js new file mode 100644 index 000000000..41fd718f6 --- /dev/null +++ b/modules/account/front/create/index.js @@ -0,0 +1,15 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + return this.$.watcher.submit().then(res => { + this.$state.go('account.card.basicData', {id: res.data.id}); + }); + } +} + +ngModule.component('vnUserCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/descriptor/__snapshots__/index.spec.js.snap b/modules/account/front/descriptor/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..de5f8e8c2 --- /dev/null +++ b/modules/account/front/descriptor/__snapshots__/index.spec.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`component vnUserDescriptor onPassChange() should throw an error when password is empty 1`] = `"You must enter a new password"`; + +exports[`component vnUserDescriptor onPassChange() should throw an error when repeat password not matches new password 1`] = `"Passwords don't match"`; diff --git a/modules/account/front/descriptor/index.html b/modules/account/front/descriptor/index.html new file mode 100644 index 000000000..88b1a9c6d --- /dev/null +++ b/modules/account/front/descriptor/index.html @@ -0,0 +1,164 @@ + + + + Delete + + + Change role + + + Change password + + + Set password + + + Enable account + + + Disable account + + + Activate user + + + Deactivate user + + + +
    + + + + +
    +
    + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/account/front/descriptor/index.js b/modules/account/front/descriptor/index.js new file mode 100644 index 000000000..3f27b1f76 --- /dev/null +++ b/modules/account/front/descriptor/index.js @@ -0,0 +1,123 @@ +import ngModule from '../module'; +import Descriptor from 'salix/components/descriptor'; +import UserError from 'core/lib/user-error'; + +class Controller extends Descriptor { + get user() { + return this.entity; + } + + set user(value) { + this.entity = value; + } + + get entity() { + return super.entity; + } + + set entity(value) { + super.entity = value; + this.hasAccount = null; + if (!value) return; + + this.$http.get(`UserAccounts/${value.id}/exists`) + .then(res => this.hasAccount = res.data.exists); + } + + onDelete() { + return this.$http.delete(`Accounts/${this.id}`) + .then(() => this.$state.go('account.index')) + .then(() => this.vnApp.showSuccess(this.$t('User removed'))); + } + + onChangeRole() { + this.newRole = this.user.role.id; + this.$.changeRole.show(); + } + + onChangeRoleAccept() { + const params = {roleFk: this.newRole}; + return this.$http.patch(`Accounts/${this.id}`, params) + .then(() => { + this.emit('change'); + this.vnApp.showSuccess(this.$t('Role changed succesfully!')); + }); + } + + onChangePassClick(askOldPass) { + this.$http.get('UserPasswords/findOne') + .then(res => { + this.passRequirements = res.data; + this.askOldPass = askOldPass; + this.$.changePass.show(); + }); + } + + onPassChange() { + if (!this.newPassword) + throw new UserError(`You must enter a new password`); + if (this.newPassword != this.repeatPassword) + throw new UserError(`Passwords don't match`); + + let method; + const params = {newPassword: this.newPassword}; + + if (this.askOldPass) { + method = 'changePassword'; + params.oldPassword = this.oldPassword; + } else + method = 'setPassword'; + + return this.$http.patch(`Accounts/${this.id}/${method}`, params) + .then(() => { + this.emit('change'); + this.vnApp.showSuccess(this.$t('Password changed succesfully!')); + }); + } + + onPassClose() { + this.oldPassword = ''; + this.newPassword = ''; + this.repeatPassword = ''; + this.$.$apply(); + } + + onEnableAccount() { + return this.$http.post(`UserAccounts`, {id: this.id}) + .then(() => this.onSwitchAccount(true)); + } + + onDisableAccount() { + return this.$http.delete(`UserAccounts/${this.id}`) + .then(() => this.onSwitchAccount(false)); + } + + onSwitchAccount(enable) { + this.hasAccount = enable; + const message = enable + ? 'Account enabled!' + : 'Account disabled!'; + this.emit('change'); + this.vnApp.showSuccess(this.$t(message)); + } + + onSetActive(active) { + return this.$http.patch(`Accounts/${this.id}`, {active}) + .then(() => { + this.user.active = active; + const message = active + ? 'User activated!' + : 'User deactivated!'; + this.emit('change'); + this.vnApp.showSuccess(this.$t(message)); + }); + } +} + +ngModule.component('vnUserDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + user: '<' + } +}); diff --git a/modules/account/front/descriptor/index.spec.js b/modules/account/front/descriptor/index.spec.js new file mode 100644 index 000000000..8ee67a304 --- /dev/null +++ b/modules/account/front/descriptor/index.spec.js @@ -0,0 +1,108 @@ +import './index'; + +describe('component vnUserDescriptor', () => { + let controller; + let $httpBackend; + + let user = {id: 1, name: 'foo'}; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $httpBackend.whenGET('UserAccounts/1/exists').respond({exists: true}); + + controller = $componentController('vnUserDescriptor', {$element: null}, {user}); + jest.spyOn(controller, 'emit'); + jest.spyOn(controller.vnApp, 'showSuccess'); + })); + + describe('onDelete()', () => { + it('should delete entity and go to index', () => { + controller.$state.go = jest.fn(); + + $httpBackend.expectDELETE('Accounts/1').respond(); + controller.onDelete(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('account.index'); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('onChangeRoleAccept()', () => { + it('should call backend method to change role', () => { + $httpBackend.expectPATCH('Accounts/1').respond(); + controller.onChangeRoleAccept(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.emit).toHaveBeenCalledWith('change'); + }); + }); + + describe('onPassChange()', () => { + it('should throw an error when password is empty', () => { + expect(() => { + controller.onPassChange(); + }).toThrowErrorMatchingSnapshot(); + }); + + it('should throw an error when repeat password not matches new password', () => { + controller.newPassword = 'foo'; + controller.repeatPassword = 'bar'; + + expect(() => { + controller.onPassChange(); + }).toThrowErrorMatchingSnapshot(); + }); + + it('should make a request when password checks passes', () => { + controller.newPassword = 'foo'; + controller.repeatPassword = 'foo'; + + $httpBackend.expectPATCH('Accounts/1/setPassword').respond(); + controller.onPassChange(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.emit).toHaveBeenCalledWith('change'); + }); + }); + + describe('onEnableAccount()', () => { + it('should make request to enable account', () => { + $httpBackend.expectPOST('UserAccounts', {id: 1}).respond(); + controller.onEnableAccount(); + $httpBackend.flush(); + + expect(controller.hasAccount).toBeTruthy(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.emit).toHaveBeenCalledWith('change'); + }); + }); + + describe('onDisableAccount()', () => { + it('should make request to disable account', () => { + $httpBackend.expectDELETE('UserAccounts/1').respond(); + controller.onDisableAccount(); + $httpBackend.flush(); + + expect(controller.hasAccount).toBeFalsy(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.emit).toHaveBeenCalledWith('change'); + }); + }); + + describe('onSetActive()', () => { + it('should make request to activate/deactivate the user', () => { + $httpBackend.expectPATCH('Accounts/1', {active: true}).respond(); + controller.onSetActive(true); + $httpBackend.flush(); + + expect(controller.user.active).toBeTruthy(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.emit).toHaveBeenCalledWith('change'); + }); + }); +}); diff --git a/modules/account/front/descriptor/locale/es.yml b/modules/account/front/descriptor/locale/es.yml new file mode 100644 index 000000000..5e8242819 --- /dev/null +++ b/modules/account/front/descriptor/locale/es.yml @@ -0,0 +1,31 @@ +User will be removed: El usuario será eliminado +User removed: Usuario eliminado +Are you sure you want to continue?: ¿Seguro que quieres continuar? +Account will be enabled: La cuenta será habilitada +Account will be disabled: La cuenta será deshabilitada +Account enabled!: ¡Cuenta habilitada! +Account disabled!: ¡Cuenta deshabilitada! +User will activated: El usuario será activado +User will be deactivated: El usuario será desactivado +User activated!: ¡Usuario activado! +User deactivated!: ¡Usuario desactivado! +Account enabled: Cuenta habilitada +User deactivated: Usuario desactivado +Change role: Modificar rol +Change password: Cambiar contraseña +Set password: Establecer contraseña +Enable account: Habilitar cuenta +Disable account: Deshabilitar cuenta +Activate user: Activar usuario +Deactivate user: Desactivar usuario +Old password: Contraseña antigua +New password: Nueva contraseña +Repeat password: Repetir contraseña +Password changed succesfully!: ¡Contraseña modificada correctamente! +Role changed succesfully!: ¡Rol modificado correctamente! +Password requirements: > + La contraseña debe tener al menos {{ length }} caracteres de longitud, + {{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}} + dígitos y {{nPunct}} símbolos (Ej: $%&.) +You must enter a new password: Debes introducir la nueva contraseña +Passwords don't match: Las contraseñas no coinciden diff --git a/modules/account/front/index.js b/modules/account/front/index.js new file mode 100644 index 000000000..359fdff72 --- /dev/null +++ b/modules/account/front/index.js @@ -0,0 +1,17 @@ +export * from './module'; + +import './main'; +import './index/'; +import './role'; +import './alias'; +import './connections'; +import './acl'; +import './summary'; +import './card'; +import './descriptor'; +import './search-panel'; +import './create'; +import './basic-data'; +import './mail-forwarding'; +import './aliases'; +import './roles'; diff --git a/modules/account/front/index/index.html b/modules/account/front/index/index.html new file mode 100644 index 000000000..3c967250d --- /dev/null +++ b/modules/account/front/index/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/index/index.js b/modules/account/front/index/index.js new file mode 100644 index 000000000..9324ca740 --- /dev/null +++ b/modules/account/front/index/index.js @@ -0,0 +1,14 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + preview(user) { + this.selectedUser = user; + this.$.summary.show(); + } +} + +ngModule.component('vnUserIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/index/locale/es.yml b/modules/account/front/index/locale/es.yml new file mode 100644 index 000000000..074fb054e --- /dev/null +++ b/modules/account/front/index/locale/es.yml @@ -0,0 +1,2 @@ +New user: Nuevo usuario +View user: Ver usuario \ No newline at end of file diff --git a/modules/account/front/locale/es.yml b/modules/account/front/locale/es.yml new file mode 100644 index 000000000..02c58a8b0 --- /dev/null +++ b/modules/account/front/locale/es.yml @@ -0,0 +1,10 @@ +Active: Activo +Connections: Conexiones +Description: Descripción +Name: Nombre +Nickname: Nombre mostrado +Personal email: Correo personal +Role: Rol +Mail aliases: Alias de correo +Account not enabled: Cuenta no habilitada +Inherited roles: Roles heredados diff --git a/modules/account/front/mail-forwarding/index.html b/modules/account/front/mail-forwarding/index.html new file mode 100644 index 000000000..a6be2782a --- /dev/null +++ b/modules/account/front/mail-forwarding/index.html @@ -0,0 +1,43 @@ +
    + + +
    + + + + + + + + + + +
    +
    +
    + Account not enabled +
    diff --git a/modules/account/front/mail-forwarding/index.js b/modules/account/front/mail-forwarding/index.js new file mode 100644 index 000000000..5118e8eab --- /dev/null +++ b/modules/account/front/mail-forwarding/index.js @@ -0,0 +1,12 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section {} + +ngModule.component('vnUserMailForwarding', { + template: require('./index.html'), + controller: Controller, + require: { + card: '^vnUserCard' + }, +}); diff --git a/modules/account/front/mail-forwarding/locale/es.yml b/modules/account/front/mail-forwarding/locale/es.yml new file mode 100644 index 000000000..0322e3e42 --- /dev/null +++ b/modules/account/front/mail-forwarding/locale/es.yml @@ -0,0 +1,6 @@ +Mail forwarding: Reenvío de correo +Forward email: Dirección de reenvío +Enable mail forwarding: Habilitar redirección de correo +All emails will be forwarded to the specified address.: > + Todos los correos serán reenviados a la dirección especificada, no se + mantendrá copia de los mismos en el buzón del usuario. diff --git a/modules/account/front/main/index.html b/modules/account/front/main/index.html new file mode 100644 index 000000000..5736b3a3b --- /dev/null +++ b/modules/account/front/main/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/main/index.js b/modules/account/front/main/index.js new file mode 100644 index 000000000..a43ffb76b --- /dev/null +++ b/modules/account/front/main/index.js @@ -0,0 +1,39 @@ +import ngModule from '../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class User extends ModuleMain { + constructor($element, $) { + super($element, $); + this.filter = { + fields: ['id', 'nickname', 'name', 'role'], + include: { + relation: 'role', + scope: { + fields: ['id', 'name'] + } + } + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {or: [ + {name: {like: `%${value}%`}}, + {nickname: {like: `%${value}%`}} + ]}; + case 'name': + case 'nickname': + return {[param]: {like: `%${value}%`}}; + case 'roleFk': + return {[param]: value}; + } + } +} + +ngModule.vnComponent('vnUser', { + controller: User, + template: require('./index.html') +}); diff --git a/modules/account/front/main/index.spec.js b/modules/account/front/main/index.spec.js new file mode 100644 index 000000000..c232aa849 --- /dev/null +++ b/modules/account/front/main/index.spec.js @@ -0,0 +1,28 @@ +import './index'; + +describe('component vnUser', () => { + let controller; + + beforeEach(ngModule('account')); + + beforeEach(inject($componentController => { + controller = $componentController('vnUser', {$element: null}); + })); + + describe('exprBuilder()', () => { + it('should search by id when only digits string is passed', () => { + let expr = controller.exprBuilder('search', '1'); + + expect(expr).toEqual({id: '1'}); + }); + + it('should search by name when non-only digits string is passed', () => { + let expr = controller.exprBuilder('search', '1foo'); + + expect(expr).toEqual({or: [ + {name: {like: '%1foo%'}}, + {nickname: {like: '%1foo%'}} + ]}); + }); + }); +}); diff --git a/modules/account/front/module.js b/modules/account/front/module.js new file mode 100644 index 000000000..0002f0b7a --- /dev/null +++ b/modules/account/front/module.js @@ -0,0 +1,3 @@ +import {ng} from 'core/vendor'; + +export default ng.module('account', ['vnCore']); diff --git a/modules/account/front/role/basic-data/index.html b/modules/account/front/role/basic-data/index.html new file mode 100644 index 000000000..e79601dcc --- /dev/null +++ b/modules/account/front/role/basic-data/index.html @@ -0,0 +1,39 @@ + + +
    + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/modules/account/front/role/basic-data/index.js b/modules/account/front/role/basic-data/index.js new file mode 100644 index 000000000..4e26906ee --- /dev/null +++ b/modules/account/front/role/basic-data/index.js @@ -0,0 +1,12 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section {} + +ngModule.component('vnRoleBasicData', { + template: require('./index.html'), + controller: Controller, + bindings: { + role: '<' + } +}); diff --git a/modules/account/front/role/card/index.html b/modules/account/front/role/card/index.html new file mode 100644 index 000000000..2f51f88b5 --- /dev/null +++ b/modules/account/front/role/card/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/account/front/role/card/index.js b/modules/account/front/role/card/index.js new file mode 100644 index 000000000..6f888211d --- /dev/null +++ b/modules/account/front/role/card/index.js @@ -0,0 +1,14 @@ +import ngModule from '../../module'; +import ModuleCard from 'salix/components/module-card'; + +class Controller extends ModuleCard { + reload() { + this.$http.get(`Roles/${this.$params.id}`) + .then(res => this.role = res.data); + } +} + +ngModule.vnComponent('vnRoleCard', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/role/card/index.spec.js b/modules/account/front/role/card/index.spec.js new file mode 100644 index 000000000..f39840e5f --- /dev/null +++ b/modules/account/front/role/card/index.spec.js @@ -0,0 +1,25 @@ +import './index'; + +describe('component vnRoleCard', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRoleCard', {$element: null}); + })); + + describe('reload()', () => { + it('should reload the controller data', () => { + controller.$params.id = 1; + + $httpBackend.expectGET('Roles/1').respond('foo'); + controller.reload(); + $httpBackend.flush(); + + expect(controller.role).toBe('foo'); + }); + }); +}); diff --git a/modules/account/front/role/create/index.html b/modules/account/front/role/create/index.html new file mode 100644 index 000000000..f610f6d23 --- /dev/null +++ b/modules/account/front/role/create/index.html @@ -0,0 +1,33 @@ + + +
    + + + + + + + + + + +
    diff --git a/modules/account/front/role/create/index.js b/modules/account/front/role/create/index.js new file mode 100644 index 000000000..3f7fcc9cf --- /dev/null +++ b/modules/account/front/role/create/index.js @@ -0,0 +1,15 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + return this.$.watcher.submit().then(res => + this.$state.go('account.role.card.basicData', {id: res.data.id}) + ); + } +} + +ngModule.component('vnRoleCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/role/descriptor/index.html b/modules/account/front/role/descriptor/index.html new file mode 100644 index 000000000..4cd4ac822 --- /dev/null +++ b/modules/account/front/role/descriptor/index.html @@ -0,0 +1,27 @@ + + + + Delete + + + +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/modules/account/front/role/descriptor/index.js b/modules/account/front/role/descriptor/index.js new file mode 100644 index 000000000..a1b578133 --- /dev/null +++ b/modules/account/front/role/descriptor/index.js @@ -0,0 +1,26 @@ +import ngModule from '../../module'; +import Descriptor from 'salix/components/descriptor'; + +class Controller extends Descriptor { + get role() { + return this.entity; + } + + set role(value) { + this.entity = value; + } + + onDelete() { + return this.$http.delete(`Roles/${this.id}`) + .then(() => this.$state.go('account.role')) + .then(() => this.vnApp.showSuccess(this.$t('Role removed'))); + } +} + +ngModule.component('vnRoleDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + role: '<' + } +}); diff --git a/modules/account/front/role/descriptor/index.spec.js b/modules/account/front/role/descriptor/index.spec.js new file mode 100644 index 000000000..e2761c639 --- /dev/null +++ b/modules/account/front/role/descriptor/index.spec.js @@ -0,0 +1,29 @@ +import './index'; + +describe('component vnRoleDescriptor', () => { + let controller; + let $httpBackend; + + let role = {id: 1, name: 'foo'}; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRoleDescriptor', {$element: null}, {role}); + })); + + describe('onDelete()', () => { + it('should delete entity and go to index', () => { + controller.$state.go = jest.fn(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectDELETE('Roles/1').respond(); + controller.onDelete(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('account.role'); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/account/front/role/descriptor/locale/es.yml b/modules/account/front/role/descriptor/locale/es.yml new file mode 100644 index 000000000..1ca512e4f --- /dev/null +++ b/modules/account/front/role/descriptor/locale/es.yml @@ -0,0 +1,2 @@ +Role will be removed: El rol va a ser eliminado +Role removed: Rol eliminado \ No newline at end of file diff --git a/modules/account/front/role/index.js b/modules/account/front/role/index.js new file mode 100644 index 000000000..97a20d3bc --- /dev/null +++ b/modules/account/front/role/index.js @@ -0,0 +1,10 @@ +import './main'; +import './index/'; +import './summary'; +import './card'; +import './descriptor'; +import './search-panel'; +import './create'; +import './basic-data'; +import './subroles'; +import './inherited'; diff --git a/modules/account/front/role/index/index.html b/modules/account/front/role/index/index.html new file mode 100644 index 000000000..92cca58d9 --- /dev/null +++ b/modules/account/front/role/index/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/role/index/index.js b/modules/account/front/role/index/index.js new file mode 100644 index 000000000..40773b23b --- /dev/null +++ b/modules/account/front/role/index/index.js @@ -0,0 +1,14 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + preview(role) { + this.selectedRole = role; + this.$.summary.show(); + } +} + +ngModule.component('vnRoleIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/role/index/locale/es.yml b/modules/account/front/role/index/locale/es.yml new file mode 100644 index 000000000..70932e983 --- /dev/null +++ b/modules/account/front/role/index/locale/es.yml @@ -0,0 +1,2 @@ +New role: Nuevo rol +View role: Ver rol \ No newline at end of file diff --git a/modules/account/front/role/inherited/index.html b/modules/account/front/role/inherited/index.html new file mode 100644 index 000000000..83ecbbff4 --- /dev/null +++ b/modules/account/front/role/inherited/index.html @@ -0,0 +1,21 @@ + + + + + +
    + {{::row.inherits.name}} +
    +
    + {{::row.inherits.description}} +
    +
    +
    +
    +
    +
    diff --git a/modules/account/front/role/inherited/index.js b/modules/account/front/role/inherited/index.js new file mode 100644 index 000000000..5927493ee --- /dev/null +++ b/modules/account/front/role/inherited/index.js @@ -0,0 +1,23 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + $onInit() { + let filter = { + where: {role: this.$params.id}, + include: { + relation: 'inherits', + scope: { + fields: ['id', 'name', 'description'] + } + } + }; + this.$http.get('RoleRoles', {filter}) + .then(res => this.$.data = res.data); + } +} + +ngModule.component('vnRoleInherited', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/role/inherited/index.spec.js b/modules/account/front/role/inherited/index.spec.js new file mode 100644 index 000000000..16b0c53b2 --- /dev/null +++ b/modules/account/front/role/inherited/index.spec.js @@ -0,0 +1,23 @@ +import './index'; + +describe('component vnRoleInherited', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRoleInherited', {$element: null}); + })); + + describe('$onInit()', () => { + it('should delete entity and go to index', () => { + $httpBackend.expectGET('RoleRoles').respond('foo'); + controller.$onInit(); + $httpBackend.flush(); + + expect(controller.$.data).toBe('foo'); + }); + }); +}); diff --git a/modules/account/front/role/locale/es.yml b/modules/account/front/role/locale/es.yml new file mode 100644 index 000000000..159fc7f16 --- /dev/null +++ b/modules/account/front/role/locale/es.yml @@ -0,0 +1 @@ +Subroles: Subroles diff --git a/modules/account/front/role/main/index.html b/modules/account/front/role/main/index.html new file mode 100644 index 000000000..9d7e6e053 --- /dev/null +++ b/modules/account/front/role/main/index.html @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/modules/account/front/role/main/index.js b/modules/account/front/role/main/index.js new file mode 100644 index 000000000..77d15cf17 --- /dev/null +++ b/modules/account/front/role/main/index.js @@ -0,0 +1,24 @@ +import ngModule from '../../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class Role extends ModuleMain { + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {or: [ + {name: {like: `%${value}%`}}, + {nickname: {like: `%${value}%`}} + ]}; + case 'name': + case 'description': + return {[param]: {like: `%${value}%`}}; + } + } +} + +ngModule.vnComponent('vnRole', { + controller: Role, + template: require('./index.html') +}); diff --git a/modules/account/front/role/search-panel/index.html b/modules/account/front/role/search-panel/index.html new file mode 100644 index 000000000..dfea9f01c --- /dev/null +++ b/modules/account/front/role/search-panel/index.html @@ -0,0 +1,21 @@ +
    +
    + + + + + + + + + + + +
    +
    \ No newline at end of file diff --git a/modules/account/front/role/search-panel/index.js b/modules/account/front/role/search-panel/index.js new file mode 100644 index 000000000..35da591ad --- /dev/null +++ b/modules/account/front/role/search-panel/index.js @@ -0,0 +1,7 @@ +import ngModule from '../../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +ngModule.component('vnRoleSearchPanel', { + template: require('./index.html'), + controller: SearchPanel +}); diff --git a/modules/account/front/role/subroles/index.html b/modules/account/front/role/subroles/index.html new file mode 100644 index 000000000..56c6770c3 --- /dev/null +++ b/modules/account/front/role/subroles/index.html @@ -0,0 +1,57 @@ + + + + + +
    + {{::row.inherits.name}} +
    +
    + {{::row.inherits.description}} +
    +
    + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/modules/account/front/role/subroles/index.js b/modules/account/front/role/subroles/index.js new file mode 100644 index 000000000..b7e1caaa4 --- /dev/null +++ b/modules/account/front/role/subroles/index.js @@ -0,0 +1,51 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + $onInit() { + this.refresh(); + } + + get path() { + return `RoleInherits`; + } + + refresh() { + let filter = { + where: {role: this.$params.id}, + include: { + relation: 'inherits', + scope: { + fields: ['id', 'name', 'description'] + } + } + }; + this.$http.get(this.path, {filter}) + .then(res => this.$.data = res.data); + } + + onAddClick() { + this.addData = {role: this.$params.id}; + this.$.dialog.show(); + } + + onAddSave() { + return this.$http.post(this.path, this.addData) + .then(() => this.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('Role added! Changes will take a while to fully propagate.'))); + } + + onRemove(row) { + return this.$http.delete(`${this.path}/${row.id}`) + .then(() => { + let index = this.$.data.indexOf(row); + if (index !== -1) this.$.data.splice(index, 1); + this.vnApp.showSuccess(this.$t('Role removed. Changes will take a while to fully propagate.')); + }); + } +} + +ngModule.component('vnRoleSubroles', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/role/subroles/index.spec.js b/modules/account/front/role/subroles/index.spec.js new file mode 100644 index 000000000..e7d9a4d0e --- /dev/null +++ b/modules/account/front/role/subroles/index.spec.js @@ -0,0 +1,53 @@ +import './index'; + +describe('component vnRoleSubroles', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('account')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRoleSubroles', {$element: null}); + jest.spyOn(controller.vnApp, 'showSuccess'); + })); + + describe('refresh()', () => { + it('should delete entity and go to index', () => { + $httpBackend.expectGET('RoleInherits').respond('foo'); + controller.refresh(); + $httpBackend.flush(); + + expect(controller.$.data).toBe('foo'); + }); + }); + + describe('onAddSave()', () => { + it('should add a subrole', () => { + controller.addData = {role: 'foo'}; + + $httpBackend.expectPOST('RoleInherits', {role: 'foo'}).respond(); + $httpBackend.expectGET('RoleInherits').respond(); + controller.onAddSave(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('onRemove()', () => { + it('should remove a subrole', () => { + controller.$.data = [ + {id: 1, name: 'foo'}, + {id: 2, name: 'bar'} + ]; + + $httpBackend.expectDELETE('RoleInherits/1').respond(); + controller.onRemove(controller.$.data[0]); + $httpBackend.flush(); + + expect(controller.$.data).toEqual([{id: 2, name: 'bar'}]); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/account/front/role/subroles/locale/es.yml b/modules/account/front/role/subroles/locale/es.yml new file mode 100644 index 000000000..170882405 --- /dev/null +++ b/modules/account/front/role/subroles/locale/es.yml @@ -0,0 +1,4 @@ +Role added! Changes will take a while to fully propagate.: > + ¡Rol añadido! Los cambios tardaran un tiempo en propagarse completamente. +Role removed. Changes will take a while to fully propagate.: > + Rol eliminado. Los cambios tardaran un tiempo en propagarse completamente. diff --git a/modules/account/front/role/summary/index.html b/modules/account/front/role/summary/index.html new file mode 100644 index 000000000..f7971190c --- /dev/null +++ b/modules/account/front/role/summary/index.html @@ -0,0 +1,20 @@ + +
    {{summary.name}}
    + + +

    Basic data

    + + + + + + +
    +
    +
    \ No newline at end of file diff --git a/modules/account/front/role/summary/index.js b/modules/account/front/role/summary/index.js new file mode 100644 index 000000000..0a08fe8b2 --- /dev/null +++ b/modules/account/front/role/summary/index.js @@ -0,0 +1,25 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; + +class Controller extends Component { + set role(value) { + this._role = value; + this.$.summary = null; + if (!value) return; + + this.$http.get(`Roles/${value.id}`) + .then(res => this.$.summary = res.data); + } + + get role() { + return this._role; + } +} + +ngModule.component('vnRoleSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + role: '<' + } +}); diff --git a/modules/account/front/roles/index.html b/modules/account/front/roles/index.html new file mode 100644 index 000000000..8c8583929 --- /dev/null +++ b/modules/account/front/roles/index.html @@ -0,0 +1,21 @@ + + + + + +
    + {{::row.role.name}} +
    +
    + {{::row.role.description}} +
    +
    +
    +
    +
    +
    diff --git a/modules/account/front/roles/index.js b/modules/account/front/roles/index.js new file mode 100644 index 000000000..0982dcf10 --- /dev/null +++ b/modules/account/front/roles/index.js @@ -0,0 +1,26 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + $onInit() { + let filter = { + where: { + prindicpalType: 'USER', + principalId: this.$params.id + }, + include: { + relation: 'role', + scope: { + fields: ['id', 'name', 'description'] + } + } + }; + this.$http.get('RoleMappings', {filter}) + .then(res => this.$.data = res.data); + } +} + +ngModule.component('vnUserRoles', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/account/front/routes.json b/modules/account/front/routes.json new file mode 100644 index 000000000..7316fff94 --- /dev/null +++ b/modules/account/front/routes.json @@ -0,0 +1,199 @@ +{ + "module": "account", + "name": "Users", + "icon" : "face", + "validations" : true, + "dependencies": [], + "menus": { + "main": [ + {"state": "account.index", "icon": "face"}, + {"state": "account.role", "icon": "group"}, + {"state": "account.alias", "icon": "email"}, + {"state": "account.acl", "icon": "check"}, + {"state": "account.connections", "icon": "share"} + ], + "card": [ + {"state": "account.card.basicData", "icon": "settings"}, + {"state": "account.card.roles", "icon": "group"}, + {"state": "account.card.mailForwarding", "icon": "forward"}, + {"state": "account.card.aliases", "icon": "email"} + ], + "role": [ + {"state": "account.role.card.basicData", "icon": "settings"}, + {"state": "account.role.card.subroles", "icon": "groups"}, + {"state": "account.role.card.inherited", "icon": "account_tree"} + ], + "alias": [ + {"state": "account.alias.card.basicData", "icon": "settings"}, + {"state": "account.alias.card.users", "icon": "groups"} + ] + }, + "routes": [ + { + "url": "/account", + "state": "account", + "component": "vn-user", + "description": "Users", + "abstract": true + }, { + "url": "/index?q", + "state": "account.index", + "component": "vn-user-index", + "description": "Users" + }, { + "url": "/create", + "state": "account.create", + "component": "vn-user-create", + "description": "New user" + }, { + "url": "/:id", + "state": "account.card", + "component": "vn-user-card", + "abstract": true, + "description": "Detail" + }, { + "url": "/summary", + "state": "account.card.summary", + "component": "vn-user-summary", + "description": "Summary", + "params": { + "user": "$ctrl.user" + } + }, { + "url": "/basic-data", + "state": "account.card.basicData", + "component": "vn-user-basic-data", + "description": "Basic data", + "params": { + "user": "$ctrl.user" + } + }, { + "url": "/roles", + "state": "account.card.roles", + "component": "vn-user-roles", + "description": "Inherited roles", + "params": { + "user": "$ctrl.user" + } + }, { + "url": "/mail-forwarding", + "state": "account.card.mailForwarding", + "component": "vn-user-mail-forwarding", + "description": "Mail forwarding", + "params": { + "user": "$ctrl.user" + } + }, { + "url": "/aliases", + "state": "account.card.aliases", + "component": "vn-user-aliases", + "description": "Mail aliases", + "params": { + "user": "$ctrl.user" + } + }, { + "url": "/role?q", + "state": "account.role", + "component": "vn-role", + "description": "Roles" + }, { + "url": "/create", + "state": "account.role.create", + "component": "vn-role-create", + "description": "New role" + }, { + "url": "/:id", + "state": "account.role.card", + "component": "vn-role-card", + "abstract": true, + "description": "Detail" + }, { + "url": "/summary", + "state": "account.role.card.summary", + "component": "vn-role-summary", + "description": "Summary", + "params": { + "role": "$ctrl.role" + } + }, { + "url": "/basic-data", + "state": "account.role.card.basicData", + "component": "vn-role-basic-data", + "description": "Basic data", + "acl": ["developer"], + "params": { + "role": "$ctrl.role" + } + }, { + "url": "/subroles", + "state": "account.role.card.subroles", + "component": "vn-role-subroles", + "acl": ["developer"], + "description": "Subroles" + }, { + "url": "/inherited", + "state": "account.role.card.inherited", + "component": "vn-role-inherited", + "description": "Inherited roles" + }, { + "url": "/alias?q", + "state": "account.alias", + "component": "vn-alias", + "description": "Mail aliases", + "acl": ["developer"] + }, { + "url": "/create", + "state": "account.alias.create", + "component": "vn-alias-create", + "description": "New alias" + }, { + "url": "/:id", + "state": "account.alias.card", + "component": "vn-alias-card", + "abstract": true, + "description": "Detail" + }, { + "url": "/summary", + "state": "account.alias.card.summary", + "component": "vn-alias-summary", + "description": "Summary", + "params": { + "alias": "$ctrl.alias" + } + }, { + "url": "/basic-data", + "state": "account.alias.card.basicData", + "component": "vn-alias-basic-data", + "description": "Basic data", + "params": { + "alias": "$ctrl.alias" + } + }, { + "url": "/users", + "state": "account.alias.card.users", + "component": "vn-alias-users", + "description": "Users" + }, { + "url": "/acl?q", + "state": "account.acl", + "component": "vn-acl-component", + "description": "ACLs", + "acl": ["developer"] + }, { + "url": "/create", + "state": "account.acl.create", + "component": "vn-acl-create", + "description": "New ACL" + }, { + "url": "/:id/edit", + "state": "account.acl.edit", + "component": "vn-acl-create", + "description": "Edit ACL" + }, { + "url": "/connections", + "state": "account.connections", + "component": "vn-connections", + "description": "Connections" + } + ] +} \ No newline at end of file diff --git a/modules/account/front/search-panel/index.html b/modules/account/front/search-panel/index.html new file mode 100644 index 000000000..f80b537aa --- /dev/null +++ b/modules/account/front/search-panel/index.html @@ -0,0 +1,31 @@ +
    +
    + + + + + + + + + + + + + + + +
    +
    \ No newline at end of file diff --git a/modules/account/front/search-panel/index.js b/modules/account/front/search-panel/index.js new file mode 100644 index 000000000..fff3bf7b9 --- /dev/null +++ b/modules/account/front/search-panel/index.js @@ -0,0 +1,7 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +ngModule.component('vnUserSearchPanel', { + template: require('./index.html'), + controller: SearchPanel +}); diff --git a/modules/account/front/summary/index.html b/modules/account/front/summary/index.html new file mode 100644 index 000000000..7f390f17c --- /dev/null +++ b/modules/account/front/summary/index.html @@ -0,0 +1,20 @@ + +
    {{summary.nickname}}
    + + +

    Basic data

    + + + + + + +
    +
    +
    \ No newline at end of file diff --git a/modules/account/front/summary/index.js b/modules/account/front/summary/index.js new file mode 100644 index 000000000..31c2d7d69 --- /dev/null +++ b/modules/account/front/summary/index.js @@ -0,0 +1,33 @@ +import ngModule from '../module'; +import Component from 'core/lib/component'; + +class Controller extends Component { + set user(value) { + this._user = value; + this.$.summary = null; + if (!value) return; + + const filter = { + include: { + relation: 'role', + scope: { + fields: ['id', 'name'] + } + } + }; + this.$http.get(`Accounts/${value.id}`, {filter}) + .then(res => this.$.summary = res.data); + } + + get user() { + return this._user; + } +} + +ngModule.component('vnUserSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + user: '<' + } +}); diff --git a/modules/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html index 710068196..c9183a229 100644 --- a/modules/claim/front/basic-data/index.html +++ b/modules/claim/front/basic-data/index.html @@ -1,40 +1,35 @@ + data="$ctrl.claim" + insert-mode="true" + form="form">
    - - diff --git a/modules/client/back/methods/client/specs/activeWorkersWithRole.spec.js b/modules/client/back/methods/client/specs/activeWorkersWithRole.spec.js index 7eb8e7ed9..5dbf6cb48 100644 --- a/modules/client/back/methods/client/specs/activeWorkersWithRole.spec.js +++ b/modules/client/back/methods/client/specs/activeWorkersWithRole.spec.js @@ -7,7 +7,7 @@ describe('Client activeWorkersWithRole', () => { let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson'); - expect(result.length).toEqual(15); + expect(result.length).toEqual(16); expect(isSalesPerson).toBeTruthy(); }); @@ -17,7 +17,7 @@ describe('Client activeWorkersWithRole', () => { let isBuyer = await app.models.Account.hasRole(result[0].id, 'buyer'); - expect(result.length).toEqual(15); + expect(result.length).toEqual(16); expect(isBuyer).toBeTruthy(); }); }); diff --git a/modules/client/back/methods/client/specs/listWorkers.spec.js b/modules/client/back/methods/client/specs/listWorkers.spec.js index 97f4b591d..329a27aa5 100644 --- a/modules/client/back/methods/client/specs/listWorkers.spec.js +++ b/modules/client/back/methods/client/specs/listWorkers.spec.js @@ -6,7 +6,7 @@ describe('Client listWorkers', () => { .then(result => { let amountOfEmployees = Object.keys(result).length; - expect(amountOfEmployees).toEqual(50); + expect(amountOfEmployees).toEqual(51); done(); }) .catch(done.fail); diff --git a/modules/client/front/address/create/index.html b/modules/client/front/address/create/index.html index 89c674a41..1cbe3fb00 100644 --- a/modules/client/front/address/create/index.html +++ b/modules/client/front/address/create/index.html @@ -4,7 +4,7 @@ id-field="id" data="$ctrl.address" params="$ctrl.address" - save="post" + insert-mode="true" form="form"> + insert-mode="true" + form="form"> @@ -15,14 +15,13 @@ rule vn-focus> - + where="{role: 'employee'}"> {{firstName}} {{lastName}} @@ -34,7 +33,6 @@ rule> @@ -49,7 +47,7 @@ - - {{name}}, {{province.name}} @@ -88,34 +86,31 @@ - {{name}} ({{country.country}}) - + show-field="country"> diff --git a/modules/client/front/credit-insurance/insurance/create/index.html b/modules/client/front/credit-insurance/insurance/create/index.html index 2331cf40b..f3de7fbe7 100644 --- a/modules/client/front/credit-insurance/insurance/create/index.html +++ b/modules/client/front/credit-insurance/insurance/create/index.html @@ -1,15 +1,14 @@ - + insert-mode="true" + form="form">
  • + +
    diff --git a/modules/client/front/greuge/create/index.html b/modules/client/front/greuge/create/index.html index f2eeacd88..7dd6ab1a8 100644 --- a/modules/client/front/greuge/create/index.html +++ b/modules/client/front/greuge/create/index.html @@ -1,15 +1,14 @@ - + insert-mode="true"> - + \ No newline at end of file diff --git a/modules/client/front/greuge/create/index.js b/modules/client/front/greuge/create/index.js index baf9f6a49..dcc0fa522 100644 --- a/modules/client/front/greuge/create/index.js +++ b/modules/client/front/greuge/create/index.js @@ -2,31 +2,23 @@ import ngModule from '../../module'; import Section from 'salix/components/section'; class Controller extends Section { - constructor($element, $) { - super($element, $); + constructor(...args) { + super(...args); this.greuge = { shipped: new Date(), clientFk: this.$params.id }; } - cancel() { - this.goToIndex(); - } - goToIndex() { - this.$state.go('client.card.greuge.index'); + return this.$state.go('client.card.greuge.index'); } onSubmit() { - this.$.watcher.submit().then( - () => { - this.goToIndex(); - } - ); + this.$.watcher.submit() + .then(() => this.goToIndex()); } } -Controller.$inject = ['$element', '$scope']; ngModule.vnComponent('vnClientGreugeCreate', { template: require('./index.html'), diff --git a/modules/client/front/note/create/index.html b/modules/client/front/note/create/index.html index c9c351199..aba03aafb 100644 --- a/modules/client/front/note/create/index.html +++ b/modules/client/front/note/create/index.html @@ -3,7 +3,7 @@ url="ClientObservations" id-field="id" data="$ctrl.note" - save="post" + insert-mode="true" form="form">
    diff --git a/modules/client/front/recovery/create/index.html b/modules/client/front/recovery/create/index.html index 56eb7eeb7..7747bb4d7 100644 --- a/modules/client/front/recovery/create/index.html +++ b/modules/client/front/recovery/create/index.html @@ -1,34 +1,30 @@ - + insert-mode="true" + form="form"> + rule + vn-focus> diff --git a/modules/client/front/sample/create/index.html b/modules/client/front/sample/create/index.html index 22f95eb20..7df7a571e 100644 --- a/modules/client/front/sample/create/index.html +++ b/modules/client/front/sample/create/index.html @@ -1,40 +1,40 @@ - - + + - - - - - diff --git a/modules/entry/front/create/index.html b/modules/entry/front/create/index.html index 7b5dfc928..0662615ae 100644 --- a/modules/entry/front/create/index.html +++ b/modules/entry/front/create/index.html @@ -1,9 +1,9 @@ - + insert-mode="true" + form="form"> @@ -12,15 +12,13 @@ vn-tooltip="Required fields (*)"> - {{::id}} - {{::nickname}} @@ -29,13 +27,11 @@ {{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) → @@ -45,11 +41,10 @@ diff --git a/modules/item/front/botanical/index.html b/modules/item/front/botanical/index.html index b6d836b30..135a55e6f 100644 --- a/modules/item/front/botanical/index.html +++ b/modules/item/front/botanical/index.html @@ -1,19 +1,17 @@ - - + id-value="$ctrl.$params.id" + include="[{relation: 'genus'}, {relation: 'specie'}]" + auto-fill="true" + form="form"> @@ -21,19 +19,18 @@ + value-field="genus_id"> - - + + \ No newline at end of file diff --git a/modules/item/front/botanical/index.js b/modules/item/front/botanical/index.js index 61eed3ce8..3786cdb45 100644 --- a/modules/item/front/botanical/index.js +++ b/modules/item/front/botanical/index.js @@ -1,30 +1,7 @@ import ngModule from '../module'; import Section from 'salix/components/section'; -class Controller extends Section { - constructor($element, $) { - super($element, $); - this.botanical = { - itemFk: this.$params.id - }; - } - - _getBotanical() { - let filter = { - where: {itemFk: this.$params.id}, - include: [{relation: 'genus'}, {relation: 'specie'}] - }; - this.$http.get(`ItemBotanicals?filter=${JSON.stringify(filter)}`) - .then(res => { - if (res.data.length) - this.botanical = res.data[0]; - }); - } - - $onInit() { - this._getBotanical(); - } -} +class Controller extends Section {} ngModule.vnComponent('vnItemBotanical', { template: require('./index.html'), diff --git a/modules/item/front/botanical/index.spec.js b/modules/item/front/botanical/index.spec.js deleted file mode 100644 index 24e48e4ae..000000000 --- a/modules/item/front/botanical/index.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import './index.js'; - -describe('ItemBotanical', () => { - describe('Component vnItemBotanical', () => { - let $httpBackend; - let $scope; - let controller; - - beforeEach(ngModule('item')); - - beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => { - $httpBackend = _$httpBackend_; - $scope = $rootScope.$new(); - const $element = angular.element(''); - controller = $componentController('vnItemBotanical', {$element, $scope}); - controller.$params = {id: 123}; - })); - - describe('_getBotanical()', () => { - it('should request to patch the propagation of botanical status', () => { - $httpBackend.whenGET('ItemBotanicals?filter={"where":{"itemFk":123},"include":[{"relation":"genus"},{"relation":"specie"}]}').respond({data: 'item'}); - $httpBackend.expectGET('ItemBotanicals?filter={"where":{"itemFk":123},"include":[{"relation":"genus"},{"relation":"specie"}]}'); - controller.$onInit(); - $httpBackend.flush(); - }); - }); - }); -}); diff --git a/modules/item/front/create/index.html b/modules/item/front/create/index.html index e57da198c..9fb4586da 100644 --- a/modules/item/front/create/index.html +++ b/modules/item/front/create/index.html @@ -1,27 +1,25 @@ - + insert-mode="true" + form="form">
    - - @@ -34,12 +32,11 @@ -
    {{::id}}
    @@ -48,11 +45,9 @@
    - diff --git a/modules/item/front/tax/index.spec.js b/modules/item/front/tax/index.spec.js index 0aec866b2..9565a861d 100644 --- a/modules/item/front/tax/index.spec.js +++ b/modules/item/front/tax/index.spec.js @@ -46,14 +46,13 @@ describe('Item', () => { it('should perform a post to update taxes', () => { jest.spyOn(controller.$.watcher, 'notifySaved'); jest.spyOn(controller.$.watcher, 'updateOriginalData'); - controller.taxes = [ - {id: 37, countryFk: 1, taxClassFk: 1, country: {id: 1, country: 'España'}} - ]; - controller.$.watcher.data = [ - {id: 37, countryFk: 1, taxClassFk: 2, country: {id: 1, country: 'España'}} - ]; - $httpBackend.whenPOST(`Items/updateTaxes`).respond('oki doki'); + controller.$onInit(); + $httpBackend.flush(); + + controller.taxes.push({id: 3, description: 'General VAT', code: 'G'}); + + $httpBackend.whenPOST(`Items/updateTaxes`).respond(true); controller.submit(); $httpBackend.flush(); diff --git a/modules/route/front/create/index.html b/modules/route/front/create/index.html index 7f6a1f600..0956297cc 100644 --- a/modules/route/front/create/index.html +++ b/modules/route/front/create/index.html @@ -1,50 +1,41 @@ - + insert-mode="true" + form="form"> - + where="{role: 'employee'}"> - + show-field="numberPlate"> + url="AgencyModes"> diff --git a/modules/ticket/front/request/create/index.html b/modules/ticket/front/request/create/index.html index 46d96ca44..7dafbd801 100644 --- a/modules/ticket/front/request/create/index.html +++ b/modules/ticket/front/request/create/index.html @@ -1,46 +1,41 @@ - + insert-mode="true" + form="form">
    - + search-function="{firstName: $search}"> diff --git a/modules/travel/front/create/index.html b/modules/travel/front/create/index.html index 586689ff7..652d2fbb9 100644 --- a/modules/travel/front/create/index.html +++ b/modules/travel/front/create/index.html @@ -1,55 +1,43 @@ - + insert-mode="true" + form="form"> + url="AgencyModes"> + url="Warehouses"> + url="Warehouses"> diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index e75d14322..fb2264494 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -32,7 +32,13 @@ icon="person">
    -
    +
    + + +
    diff --git a/modules/zone/front/create/index.html b/modules/zone/front/create/index.html index 07ac38477..e21499cb5 100644 --- a/modules/zone/front/create/index.html +++ b/modules/zone/front/create/index.html @@ -1,9 +1,9 @@ - + insert-mode="true" + form="form"> + rule + vn-focus> @@ -57,7 +48,6 @@ { +// Issue #2437 +xdescribe('number filter', () => { const superDuperNumber = 18021984; it('should filter the number with commas by default', () => { diff --git a/print/core/filters/specs/percentage.spec.js b/print/core/filters/specs/percentage.spec.js index 0a9111cbc..af037abb6 100644 --- a/print/core/filters/specs/percentage.spec.js +++ b/print/core/filters/specs/percentage.spec.js @@ -1,6 +1,7 @@ import percentage from '../percentage.js'; -describe('percentage filter', () => { +// Issue #2437 +xdescribe('percentage filter', () => { it('should filter the percentage also round it correctly', () => { expect(percentage(99.9999999999999999 / 100)).toEqual('100.00%'); }); -- 2.40.1 From 901c00066f5eaee7f9235e2e1dfda683d0de5f23 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 11 Sep 2020 14:47:23 +0200 Subject: [PATCH 2/7] Front test fixed --- front/core/components/searchbar/searchbar.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 58fdadb65..8adc40b67 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -210,9 +210,7 @@ export default class Searchbar extends Component { && source != 'state' && !angular.equals(filter, {}) && data - && data.length == 1 - && filter.search - && Object.keys(filter).length == 1; + && data.length == 1; if (isOneResult) { let baseDepth = this.baseState.split('.').length; -- 2.40.1 From b9c526c29c262a6e42338afd2149fe9d9ded1762 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 21 Sep 2020 13:24:43 +0200 Subject: [PATCH 3/7] LDAP user & role syncronization --- back/methods/account/change-password.js | 38 +-- back/methods/account/login.js | 17 +- back/methods/account/set-password.js | 10 +- back/models/account.json | 51 ++-- db/changes/10211-accountModule/00-account.sql | 6 + .../00-user_setPassword.sql | 2 - db/dump/dumpedFixtures.sql | 124 ++++---- db/dump/fixtures.sql | 17 +- db/export-data.sh | 3 + loopback/locale/es.json | 4 +- .../account/back/methods/role-inherit/sync.js | 114 ++++++++ .../back/methods/user-account/sync-by-id.js | 27 ++ .../account/back/methods/user-account/sync.js | 268 ++++++++++++++++++ modules/account/back/model-config.json | 13 +- .../account/back/models/account-config.json | 43 +++ modules/account/back/models/ldap-config.json | 36 +++ modules/account/back/models/mail-config.json | 19 ++ .../models/{role-role.js => role-inherit.js} | 4 +- modules/account/back/models/samba-config.json | 25 ++ modules/account/back/models/user-account.js | 5 + modules/account/back/util/ldapjs-extra.js | 30 ++ modules/account/back/util/promisify.js | 47 +++ modules/account/front/basic-data/index.html | 2 +- modules/account/front/connections/index.html | 2 +- .../account/front/role/subroles/index.html | 2 +- package-lock.json | 83 +++++- package.json | 2 + 27 files changed, 842 insertions(+), 152 deletions(-) create mode 100644 modules/account/back/methods/role-inherit/sync.js create mode 100644 modules/account/back/methods/user-account/sync-by-id.js create mode 100644 modules/account/back/methods/user-account/sync.js create mode 100644 modules/account/back/models/account-config.json create mode 100644 modules/account/back/models/ldap-config.json create mode 100644 modules/account/back/models/mail-config.json rename modules/account/back/models/{role-role.js => role-inherit.js} (86%) create mode 100644 modules/account/back/models/samba-config.json create mode 100644 modules/account/back/models/user-account.js create mode 100644 modules/account/back/util/ldapjs-extra.js create mode 100644 modules/account/back/util/promisify.js diff --git a/back/methods/account/change-password.js b/back/methods/account/change-password.js index b2afd5402..25b63b9a8 100644 --- a/back/methods/account/change-password.js +++ b/back/methods/account/change-password.js @@ -4,10 +4,6 @@ module.exports = Self => { description: 'Changes the user password', accepts: [ { - arg: 'ctx', - type: 'Object', - http: {source: 'context'} - }, { arg: 'id', type: 'Number', description: 'The user id', @@ -24,41 +20,15 @@ module.exports = Self => { required: true } ], - returns: { - type: 'Boolean', - root: true - }, http: { path: `/:id/changePassword`, verb: 'PATCH' } }); - Self.changePassword = async function(ctx, id, oldPassword, newPassword) { - let params = [id, oldPassword, newPassword]; - await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`, params); - - /* - const ldap = require('ldapjs'); - - let ldapConf = { - url: 'ldap://domain.local:389', - dn: 'cn=admin,dc=domain,dc=local', - password: '123456' - }; - - await new Promise((reject, resolve) => { - let client = ldap.createClient({url: ldapConf.url}); - - client.bind(ldapConf.dn, ldapConf.password, err => { - if (err) - reject(err); - else - resolve(); - }); - }); - */ - - return true; + Self.changePassword = async function(id, oldPassword, newPassword) { + await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`, + [id, oldPassword, newPassword]); + await Self.app.models.UserAccount.syncById(id, newPassword); }; }; diff --git a/back/methods/account/login.js b/back/methods/account/login.js index 075d3669c..340300e23 100644 --- a/back/methods/account/login.js +++ b/back/methods/account/login.js @@ -26,9 +26,9 @@ module.exports = Self => { }); Self.login = async function(user, password) { + let $ = Self.app.models; let token; let usesEmail = user.indexOf('@') !== -1; - let User = Self.app.models.User; let loginInfo = {password}; @@ -38,7 +38,7 @@ module.exports = Self => { loginInfo.username = user; try { - token = await User.login(loginInfo, 'user'); + token = await $.User.login(loginInfo, 'user'); } catch (err) { if (err.code != 'LOGIN_FAILED' || usesEmail) throw err; @@ -49,17 +49,8 @@ module.exports = Self => { if (!instance || instance.password !== md5(password || '')) throw err; - let where = {id: instance.id}; - let userData = { - id: instance.id, - username: user, - password: password, - email: instance.email, - created: instance.created, - updated: instance.updated - }; - await User.upsertWithWhere(where, userData); - token = await User.login(loginInfo, 'user'); + await $.UserAccount.sync(user, password); + token = await $.User.login(loginInfo, 'user'); } return {token: token.id}; diff --git a/back/methods/account/set-password.js b/back/methods/account/set-password.js index 027649548..fc54b5abe 100644 --- a/back/methods/account/set-password.js +++ b/back/methods/account/set-password.js @@ -15,10 +15,6 @@ module.exports = Self => { required: true } ], - returns: { - type: 'Boolean', - root: true - }, http: { path: `/:id/setPassword`, verb: 'PATCH' @@ -26,8 +22,8 @@ module.exports = Self => { }); Self.setPassword = async function(id, newPassword) { - let params = [id, newPassword]; - await Self.rawSql(`CALL account.user_setPassword(?, ?)`, params); - return true; + await Self.rawSql(`CALL account.user_setPassword(?, ?)`, + [id, newPassword]); + await Self.app.models.UserAccount.syncById(id, newPassword); }; }; diff --git a/back/models/account.json b/back/models/account.json index fbf736f03..d29e1f38c 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -5,7 +5,7 @@ "mysql": { "table": "account.user" } - }, + }, "properties": { "id": { "type": "number", @@ -31,6 +31,9 @@ "type": "string", "required": true }, + "bcryptPassword": { + "type": "string" + }, "active": { "type": "boolean" }, @@ -42,46 +45,52 @@ }, "updated": { "type": "date" + }, + "sync": { + "type": "boolean" } }, - "relations": { - "role": { - "type": "belongsTo", - "model": "Role", - "foreignKey": "roleFk" - }, - "emailUser": { - "type": "hasOne", - "model": "EmailUser", - "foreignKey": "userFk" + "relations": { + "role": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "roleFk" + }, + "roles": { + "type": "hasMany", + "model": "RoleRole", + "foreignKey": "role" + }, + "emailUser": { + "type": "hasOne", + "model": "EmailUser", + "foreignKey": "userFk" }, "worker": { "type": "hasOne", "model": "Worker", "foreignKey": "userFk" - } - }, - "acls": [ - { + } + }, + "acls": [ + { "property": "login", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - }, - { + }, { "property": "logout", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" - }, - { + }, { "property": "validateToken", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" - } - ] + } + ] } diff --git a/db/changes/10211-accountModule/00-account.sql b/db/changes/10211-accountModule/00-account.sql index 987256793..3204f1417 100644 --- a/db/changes/10211-accountModule/00-account.sql +++ b/db/changes/10211-accountModule/00-account.sql @@ -25,6 +25,12 @@ ALTER TABLE `account`.`mailAliasAccount` ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`id`); +ALTER TABLE account.ldapConfig + ADD groupDn varchar(255) NULL; + +UPDATE account.ldapConfig SET groupDn = 'ou=groups,dc=verdnatura,dc=es'; + +DROP PROCEDURE IF EXISTS account.user_syncPassword; USE account; diff --git a/db/changes/10211-accountModule/00-user_setPassword.sql b/db/changes/10211-accountModule/00-user_setPassword.sql index 430c60eab..3dcbb1653 100644 --- a/db/changes/10211-accountModule/00-user_setPassword.sql +++ b/db/changes/10211-accountModule/00-user_setPassword.sql @@ -17,7 +17,5 @@ BEGIN `password` = MD5(vPassword), `recoverPass` = FALSE WHERE id = vSelf; - - CALL user_syncPassword(vSelf, vPassword); END$$ DELIMITER ; diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index 9c05923cf..5f869ab6f 100644 --- a/db/dump/dumpedFixtures.sql +++ b/db/dump/dumpedFixtures.sql @@ -1,18 +1,17 @@ USE `util`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: util +-- Host: test-db.verdnatura.es Database: util -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -23,34 +22,32 @@ USE `util`; LOCK TABLES `config` WRITE; /*!40000 ALTER TABLE `config` DISABLE KEYS */; -INSERT INTO `config` VALUES (1,'10210',0,'production',NULL); +INSERT INTO `config` VALUES (1,'10210',0,'test','2020-09-11 04:06:00'); /*!40000 ALTER TABLE `config` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:28 +-- Dump completed on 2020-09-12 1:00:58 USE `account`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: account +-- Host: test-db.verdnatura.es Database: account -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -71,7 +68,7 @@ UNLOCK TABLES; LOCK TABLES `roleInherit` WRITE; /*!40000 ALTER TABLE `roleInherit` DISABLE KEYS */; -INSERT INTO `roleInherit` VALUES (9,0),(66,0),(5,1),(13,1),(18,1),(31,1),(32,1),(34,1),(35,1),(37,1),(40,1),(44,1),(47,1),(51,1),(53,1),(54,1),(56,1),(58,1),(1,2),(1,3),(30,5),(39,5),(60,5),(67,5),(11,6),(2,11),(3,11),(70,11),(16,13),(20,13),(21,13),(22,13),(34,13),(41,13),(43,13),(45,13),(48,13),(50,13),(52,13),(55,13),(57,13),(59,13),(61,13),(16,15),(20,16),(21,18),(52,19),(65,19),(17,20),(30,20),(5,21),(19,21),(22,21),(39,21),(50,21),(30,22),(5,33),(34,33),(15,35),(41,35),(42,35),(50,35),(52,35),(65,35),(69,35),(49,36),(61,36),(17,37),(38,37),(60,37),(67,37),(17,39),(41,40),(43,42),(36,44),(45,44),(36,47),(48,47),(69,47),(40,49),(42,49),(50,49),(59,49),(60,50),(65,50),(52,51),(21,53),(30,53),(55,54),(57,56),(15,57),(39,57),(50,57),(60,57),(49,58),(50,59),(17,64),(30,64),(38,64),(20,65),(1,70); +INSERT INTO `roleInherit` VALUES (1,2),(1,3),(1,70),(2,11),(3,11),(5,1),(5,21),(5,33),(9,0),(11,6),(13,1),(15,35),(15,57),(16,13),(16,15),(17,20),(17,37),(17,39),(17,64),(18,1),(19,21),(20,13),(20,16),(20,65),(21,13),(21,18),(21,53),(22,13),(22,21),(30,5),(30,20),(30,22),(30,53),(30,64),(31,1),(32,1),(34,1),(34,13),(34,33),(35,1),(36,44),(36,47),(37,1),(38,37),(38,64),(39,5),(39,21),(39,57),(40,1),(40,49),(41,13),(41,35),(41,40),(42,35),(42,49),(43,13),(43,42),(44,1),(45,13),(45,44),(47,1),(48,13),(48,47),(49,36),(49,58),(50,13),(50,21),(50,35),(50,49),(50,57),(50,59),(51,1),(52,13),(52,19),(52,35),(52,51),(53,1),(54,1),(55,13),(55,54),(56,1),(57,13),(57,56),(58,1),(59,13),(59,49),(60,5),(60,37),(60,50),(60,57),(61,13),(61,36),(65,19),(65,35),(65,50),(66,0),(67,5),(67,37),(69,35),(69,47),(70,11); /*!40000 ALTER TABLE `roleInherit` ENABLE KEYS */; UNLOCK TABLES; @@ -84,31 +81,59 @@ LOCK TABLES `roleRole` WRITE; INSERT INTO `roleRole` VALUES (0,0),(0,1),(0,2),(0,3),(0,5),(0,6),(0,9),(0,11),(0,13),(0,15),(0,16),(0,17),(0,18),(0,19),(0,20),(0,21),(0,22),(0,30),(0,31),(0,32),(0,33),(0,34),(0,35),(0,36),(0,37),(0,38),(0,39),(0,40),(0,41),(0,42),(0,43),(0,44),(0,45),(0,47),(0,48),(0,49),(0,50),(0,51),(0,52),(0,53),(0,54),(0,55),(0,56),(0,57),(0,58),(0,59),(0,60),(0,61),(0,62),(0,64),(0,65),(0,66),(0,67),(0,69),(0,70),(1,1),(1,2),(1,3),(1,6),(1,11),(1,70),(2,2),(2,6),(2,11),(3,3),(3,6),(3,11),(5,1),(5,2),(5,3),(5,5),(5,6),(5,11),(5,13),(5,18),(5,21),(5,33),(5,53),(5,70),(6,6),(9,0),(9,1),(9,2),(9,3),(9,5),(9,6),(9,9),(9,11),(9,13),(9,15),(9,16),(9,17),(9,18),(9,19),(9,20),(9,21),(9,22),(9,30),(9,31),(9,32),(9,33),(9,34),(9,35),(9,36),(9,37),(9,38),(9,39),(9,40),(9,41),(9,42),(9,43),(9,44),(9,45),(9,47),(9,48),(9,49),(9,50),(9,51),(9,52),(9,53),(9,54),(9,55),(9,56),(9,57),(9,58),(9,59),(9,60),(9,61),(9,62),(9,64),(9,65),(9,66),(9,67),(9,69),(9,70),(11,6),(11,11),(13,1),(13,2),(13,3),(13,6),(13,11),(13,13),(13,70),(15,1),(15,2),(15,3),(15,6),(15,11),(15,13),(15,15),(15,35),(15,56),(15,57),(15,70),(16,1),(16,2),(16,3),(16,6),(16,11),(16,13),(16,15),(16,16),(16,35),(16,56),(16,57),(16,70),(17,1),(17,2),(17,3),(17,5),(17,6),(17,11),(17,13),(17,15),(17,16),(17,17),(17,18),(17,19),(17,20),(17,21),(17,33),(17,35),(17,36),(17,37),(17,39),(17,44),(17,47),(17,49),(17,50),(17,53),(17,56),(17,57),(17,58),(17,59),(17,64),(17,65),(17,70),(18,1),(18,2),(18,3),(18,6),(18,11),(18,18),(18,70),(19,1),(19,2),(19,3),(19,6),(19,11),(19,13),(19,18),(19,19),(19,21),(19,53),(19,70),(20,1),(20,2),(20,3),(20,6),(20,11),(20,13),(20,15),(20,16),(20,18),(20,19),(20,20),(20,21),(20,35),(20,36),(20,44),(20,47),(20,49),(20,50),(20,53),(20,56),(20,57),(20,58),(20,59),(20,65),(20,70),(21,1),(21,2),(21,3),(21,6),(21,11),(21,13),(21,18),(21,21),(21,53),(21,70),(22,1),(22,2),(22,3),(22,6),(22,11),(22,13),(22,18),(22,21),(22,22),(22,53),(22,70),(30,1),(30,2),(30,3),(30,5),(30,6),(30,11),(30,13),(30,15),(30,16),(30,18),(30,19),(30,20),(30,21),(30,22),(30,30),(30,33),(30,35),(30,36),(30,44),(30,47),(30,49),(30,50),(30,53),(30,56),(30,57),(30,58),(30,59),(30,64),(30,65),(30,70),(31,1),(31,2),(31,3),(31,6),(31,11),(31,31),(31,70),(32,1),(32,2),(32,3),(32,6),(32,11),(32,32),(32,70),(33,33),(34,1),(34,2),(34,3),(34,6),(34,11),(34,13),(34,33),(34,34),(34,70),(35,1),(35,2),(35,3),(35,6),(35,11),(35,35),(35,70),(36,1),(36,2),(36,3),(36,6),(36,11),(36,36),(36,44),(36,47),(36,70),(37,1),(37,2),(37,3),(37,6),(37,11),(37,37),(37,70),(38,1),(38,2),(38,3),(38,6),(38,11),(38,37),(38,38),(38,64),(38,70),(39,1),(39,2),(39,3),(39,5),(39,6),(39,11),(39,13),(39,18),(39,21),(39,33),(39,39),(39,53),(39,56),(39,57),(39,70),(40,1),(40,2),(40,3),(40,6),(40,11),(40,36),(40,40),(40,44),(40,47),(40,49),(40,58),(40,70),(41,1),(41,2),(41,3),(41,6),(41,11),(41,13),(41,35),(41,36),(41,40),(41,41),(41,44),(41,47),(41,49),(41,58),(41,70),(42,1),(42,2),(42,3),(42,6),(42,11),(42,35),(42,36),(42,42),(42,44),(42,47),(42,49),(42,58),(42,70),(43,1),(43,2),(43,3),(43,6),(43,11),(43,13),(43,35),(43,36),(43,42),(43,43),(43,44),(43,47),(43,49),(43,58),(43,70),(44,1),(44,2),(44,3),(44,6),(44,11),(44,44),(44,70),(45,1),(45,2),(45,3),(45,6),(45,11),(45,13),(45,44),(45,45),(45,70),(47,1),(47,2),(47,3),(47,6),(47,11),(47,47),(47,70),(48,1),(48,2),(48,3),(48,6),(48,11),(48,13),(48,47),(48,48),(48,70),(49,1),(49,2),(49,3),(49,6),(49,11),(49,36),(49,44),(49,47),(49,49),(49,58),(49,70),(50,1),(50,2),(50,3),(50,6),(50,11),(50,13),(50,18),(50,21),(50,35),(50,36),(50,44),(50,47),(50,49),(50,50),(50,53),(50,56),(50,57),(50,58),(50,59),(50,70),(51,1),(51,2),(51,3),(51,6),(51,11),(51,51),(51,70),(52,1),(52,2),(52,3),(52,6),(52,11),(52,13),(52,18),(52,19),(52,21),(52,35),(52,51),(52,52),(52,53),(52,70),(53,1),(53,2),(53,3),(53,6),(53,11),(53,53),(53,70),(54,1),(54,2),(54,3),(54,6),(54,11),(54,54),(54,70),(55,1),(55,2),(55,3),(55,6),(55,11),(55,13),(55,54),(55,55),(55,70),(56,1),(56,2),(56,3),(56,6),(56,11),(56,56),(56,70),(57,1),(57,2),(57,3),(57,6),(57,11),(57,13),(57,56),(57,57),(57,70),(58,1),(58,2),(58,3),(58,6),(58,11),(58,58),(58,70),(59,1),(59,2),(59,3),(59,6),(59,11),(59,13),(59,36),(59,44),(59,47),(59,49),(59,58),(59,59),(59,70),(60,1),(60,2),(60,3),(60,5),(60,6),(60,11),(60,13),(60,18),(60,21),(60,33),(60,35),(60,36),(60,37),(60,44),(60,47),(60,49),(60,50),(60,53),(60,56),(60,57),(60,58),(60,59),(60,60),(60,70),(61,1),(61,2),(61,3),(61,6),(61,11),(61,13),(61,36),(61,44),(61,47),(61,61),(61,70),(62,62),(64,64),(65,1),(65,2),(65,3),(65,6),(65,11),(65,13),(65,18),(65,19),(65,21),(65,35),(65,36),(65,44),(65,47),(65,49),(65,50),(65,53),(65,56),(65,57),(65,58),(65,59),(65,65),(65,70),(66,0),(66,1),(66,2),(66,3),(66,5),(66,6),(66,9),(66,11),(66,13),(66,15),(66,16),(66,17),(66,18),(66,19),(66,20),(66,21),(66,22),(66,30),(66,31),(66,32),(66,33),(66,34),(66,35),(66,36),(66,37),(66,38),(66,39),(66,40),(66,41),(66,42),(66,43),(66,44),(66,45),(66,47),(66,48),(66,49),(66,50),(66,51),(66,52),(66,53),(66,54),(66,55),(66,56),(66,57),(66,58),(66,59),(66,60),(66,61),(66,62),(66,64),(66,65),(66,66),(66,67),(66,69),(66,70),(67,1),(67,2),(67,3),(67,5),(67,6),(67,11),(67,13),(67,18),(67,21),(67,33),(67,37),(67,53),(67,67),(67,70),(69,1),(69,2),(69,3),(69,6),(69,11),(69,35),(69,47),(69,69),(69,70),(70,6),(70,11),(70,70); /*!40000 ALTER TABLE `roleRole` ENABLE KEYS */; UNLOCK TABLES; + +-- +-- Dumping data for table `userPassword` +-- + +LOCK TABLES `userPassword` WRITE; +/*!40000 ALTER TABLE `userPassword` DISABLE KEYS */; +INSERT INTO `userPassword` VALUES (1,7,1,0,1,1); +/*!40000 ALTER TABLE `userPassword` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Dumping data for table `accountConfig` +-- + +LOCK TABLES `accountConfig` WRITE; +/*!40000 ALTER TABLE `accountConfig` DISABLE KEYS */; +INSERT INTO `accountConfig` VALUES (1,'/mnt/storage/homes','/bin/bash',10000,5,60,5,30); +/*!40000 ALTER TABLE `accountConfig` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Dumping data for table `mailConfig` +-- + +LOCK TABLES `mailConfig` WRITE; +/*!40000 ALTER TABLE `mailConfig` DISABLE KEYS */; +INSERT INTO `mailConfig` VALUES (1,'verdnatura.es'); +/*!40000 ALTER TABLE `mailConfig` ENABLE KEYS */; +UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:30 +-- Dump completed on 2020-09-12 1:00:59 USE `salix`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: salix +-- Host: test-db.verdnatura.es Database: salix -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -136,27 +161,25 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:30 +-- Dump completed on 2020-09-12 1:01:00 USE `vn`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: vn +-- Host: test-db.verdnatura.es Database: vn -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -227,7 +250,7 @@ UNLOCK TABLES; LOCK TABLES `tag` WRITE; /*!40000 ALTER TABLE `tag` DISABLE KEYS */; -INSERT INTO `tag` VALUES (1,'color','Color',0,0,'ink',NULL,NULL,'inkFk'),(2,NULL,'Forma',1,0,NULL,NULL,NULL,NULL),(3,NULL,'Material',1,0,NULL,NULL,NULL,NULL),(4,NULL,'Longitud',1,1,NULL,'mm',NULL,'size'),(5,NULL,'Diámetro',1,1,NULL,'mm',NULL,'diameter'),(6,NULL,'Perímetro',1,1,NULL,'mm',NULL,NULL),(7,NULL,'Ancho de la base',1,1,NULL,'mm',NULL,NULL),(8,NULL,'Altura',1,1,NULL,'mm',NULL,'size'),(9,NULL,'Volumen',1,1,NULL,'ml',NULL,NULL),(10,NULL,'Densidad',1,1,NULL,NULL,NULL,NULL),(11,NULL,'Calidad',1,0,NULL,NULL,NULL,NULL),(12,NULL,'Textura',1,0,NULL,NULL,NULL,NULL),(13,NULL,'Material del mango',1,0,NULL,NULL,NULL,NULL),(14,NULL,'Compra mínima',1,0,NULL,NULL,NULL,NULL),(15,NULL,'Nº pétalos',1,1,NULL,NULL,NULL,NULL),(16,NULL,'Ancho',1,1,NULL,'mm',NULL,NULL),(18,NULL,'Profundidad',1,1,NULL,'mm',NULL,NULL),(19,NULL,'Largo',1,1,NULL,'mm',NULL,'size'),(20,NULL,'Ancho superior',1,1,NULL,'mm',NULL,NULL),(21,NULL,'Ancho inferior',1,1,NULL,'mm',NULL,NULL),(22,NULL,'Gramaje',1,1,NULL,'g',NULL,NULL),(23,'stems','Tallos',1,1,NULL,NULL,NULL,'stems'),(24,NULL,'Estado',1,0,NULL,NULL,NULL,NULL),(25,NULL,'Color principal',0,0,'ink',NULL,NULL,NULL),(26,NULL,'Color secundario',0,0,'ink',NULL,NULL,NULL),(27,NULL,'Longitud(cm)',1,1,NULL,'cm',NULL,NULL),(28,NULL,'Diámetro base',1,1,'','mm',NULL,'diameter'),(29,NULL,'Colección',1,0,NULL,NULL,NULL,NULL),(30,NULL,'Uds / caja',1,1,NULL,NULL,NULL,NULL),(31,NULL,'Contenido',1,0,NULL,NULL,NULL,NULL),(32,NULL,'Peso',1,1,NULL,'g',NULL,NULL),(33,NULL,'Grosor',1,1,NULL,'mm',NULL,NULL),(34,NULL,'Marca',1,0,NULL,NULL,NULL,NULL),(35,'origin','Origen',0,0,'origin',NULL,NULL,'originFk'),(36,NULL,'Proveedor',1,0,NULL,NULL,NULL,NULL),(37,'producer','Productor',0,0,'producer',NULL,NULL,'producerFk'),(38,NULL,'Duración',1,1,NULL,'s',NULL,NULL),(39,NULL,'Flor',1,0,NULL,NULL,NULL,NULL),(40,NULL,'Soporte',1,0,NULL,NULL,NULL,NULL),(41,NULL,'Tamaño flor',1,0,NULL,NULL,NULL,NULL),(42,NULL,'Apertura',1,0,NULL,NULL,NULL,NULL),(43,NULL,'Tallo',1,0,NULL,NULL,NULL,NULL),(44,NULL,'Nº hojas',1,1,NULL,NULL,NULL,NULL),(45,NULL,'Dimensiones',1,0,NULL,NULL,NULL,NULL),(46,NULL,'Diámetro boca',1,1,NULL,'mm',NULL,NULL),(47,NULL,'Nº flores',1,1,NULL,NULL,NULL,NULL),(48,NULL,'Uds / paquete',1,1,NULL,NULL,NULL,NULL),(49,NULL,'Maceta',1,1,NULL,'cm',NULL,'diameter'),(50,NULL,'Textura flor',1,0,NULL,NULL,NULL,NULL),(51,NULL,'Textura hoja',1,0,NULL,NULL,NULL,NULL),(52,NULL,'Tipo de IVA',1,0,NULL,NULL,NULL,NULL),(53,NULL,'Tronco',1,0,NULL,NULL,NULL,NULL),(54,NULL,'Hoja',1,0,NULL,NULL,NULL,NULL),(55,NULL,'Formato',1,0,NULL,NULL,NULL,NULL),(56,NULL,'Genero',1,0,NULL,NULL,NULL,NULL),(57,NULL,'Especie',1,0,NULL,NULL,NULL,NULL),(58,NULL,'Variedad',1,0,NULL,NULL,NULL,NULL),(59,NULL,'Medida grande',1,0,NULL,NULL,NULL,NULL),(60,NULL,'Medida mediano',1,0,NULL,NULL,NULL,NULL),(61,NULL,'Medida pequeño',1,0,NULL,NULL,NULL,NULL),(63,NULL,'Recipiente interior',1,0,NULL,NULL,NULL,NULL),(64,NULL,'Material secundario',1,0,NULL,NULL,NULL,NULL),(65,NULL,'Colores',1,0,NULL,NULL,NULL,NULL),(66,NULL,'Referencia',1,0,NULL,NULL,NULL,NULL),(67,'category','Categoria',1,0,NULL,NULL,NULL,NULL),(68,NULL,'Amb',1,0,NULL,NULL,NULL,NULL),(69,NULL,'Anchura',1,1,NULL,'cm',NULL,NULL),(70,NULL,'Hueco interior',1,0,NULL,NULL,NULL,NULL),(71,NULL,'Tamaño',1,0,NULL,NULL,NULL,NULL),(72,NULL,'Color botón',1,0,NULL,NULL,NULL,NULL),(73,NULL,'Tamaño minimo del botón',1,0,NULL,NULL,NULL,NULL),(74,NULL,'Obtentor',1,0,NULL,NULL,NULL,NULL),(75,NULL,'Longitud del brote',1,0,NULL,NULL,NULL,NULL),(76,NULL,'Tallos / u.v.',1,0,NULL,NULL,NULL,NULL),(77,NULL,'Madera de',1,0,NULL,NULL,NULL,NULL),(78,NULL,'Unidad de venta',1,0,NULL,NULL,NULL,NULL),(79,NULL,'Temporal',1,0,NULL,NULL,NULL,NULL),(80,NULL,'Gramaje/tallo',1,1,NULL,'g',NULL,NULL),(81,NULL,'Peso/paquete',1,1,NULL,'g',NULL,NULL),(82,NULL,'Flexibilidad del tallo',1,0,NULL,NULL,NULL,NULL),(83,NULL,'Nº planchas',1,1,NULL,NULL,NULL,NULL),(84,NULL,'Nº páginas',1,1,NULL,NULL,NULL,NULL),(85,NULL,'Editorial',1,0,NULL,NULL,NULL,NULL),(86,NULL,'Idioma',1,0,NULL,NULL,NULL,NULL),(87,NULL,'Fecha publicación',1,0,NULL,NULL,NULL,NULL),(88,NULL,'Cubierta',1,0,NULL,NULL,NULL,NULL),(89,NULL,'Encuadernación',1,0,NULL,NULL,NULL,NULL),(90,NULL,'Autor',1,0,NULL,NULL,NULL,NULL),(91,NULL,'Envoltorio',1,0,NULL,NULL,NULL,NULL),(92,NULL,'Nombre temporal',1,0,NULL,NULL,NULL,NULL),(93,NULL,'Modelo',1,0,NULL,NULL,NULL,NULL),(94,NULL,'Producto',1,0,NULL,NULL,NULL,NULL),(95,NULL,'Título',1,0,NULL,NULL,NULL,NULL),(96,NULL,'Tomo',1,0,NULL,NULL,NULL,NULL),(97,NULL,'Articulo',1,0,NULL,NULL,NULL,NULL),(98,NULL,'Metodo de cultivo',1,0,NULL,NULL,NULL,NULL),(99,NULL,'Edad',1,0,NULL,NULL,NULL,NULL),(100,NULL,'Agotado',1,0,NULL,NULL,NULL,NULL),(101,NULL,'Altura con asa',1,1,NULL,'cm',NULL,NULL),(102,NULL,'Nº tallos',1,1,NULL,NULL,NULL,NULL),(103,NULL,'Cultivo',1,0,NULL,NULL,NULL,NULL),(104,NULL,'Sabor',1,0,NULL,NULL,NULL,NULL),(105,NULL,'Talla',1,0,NULL,NULL,NULL,NULL),(106,NULL,'Calibre',1,1,NULL,NULL,NULL,NULL),(107,NULL,'Dulzura',1,1,NULL,'bx',NULL,NULL),(108,NULL,'Piezas',1,0,NULL,NULL,NULL,NULL),(109,NULL,'Altura con patas',1,0,NULL,NULL,NULL,NULL),(110,NULL,'Envase',1,0,NULL,NULL,NULL,NULL),(111,NULL,'Nº piezas',1,0,NULL,NULL,NULL,NULL),(112,NULL,'Uso',1,0,NULL,'cm',NULL,NULL),(113,NULL,'Color luz',1,0,NULL,NULL,NULL,NULL),(114,NULL,'Capacidad',1,0,NULL,NULL,NULL,NULL),(184,NULL,'Tallos por paquete',1,0,NULL,NULL,NULL,NULL),(205,NULL,'Apertura',1,0,NULL,NULL,'S05',NULL),(219,NULL,'Altura',1,0,NULL,NULL,'S20','size'),(552,NULL,'fout kenmerk',1,0,NULL,NULL,'081',NULL),(553,NULL,'Potinhoud',1,0,NULL,NULL,'A01',NULL),(554,NULL,'Marketingconcept',1,0,NULL,NULL,'A02',NULL),(555,NULL,'Leeftijd',1,0,NULL,NULL,'A03',NULL),(556,NULL,'Uitgangsmateriaal',1,0,NULL,NULL,'A04',NULL),(557,NULL,'Kleurbehandeld',1,0,NULL,NULL,'A05','inkFk'),(558,NULL,'Verzorging: Standplaats',1,0,NULL,NULL,'A06',NULL),(559,NULL,'Verzorging: Water',1,0,NULL,NULL,'A07',NULL),(560,NULL,'Verzorging: Voeding',1,0,NULL,NULL,'A08',NULL),(561,NULL,'Verzorging: Temperatuur',1,0,NULL,NULL,'A09',NULL),(562,NULL,'Verzorging: Specifieke in',1,0,NULL,NULL,'A10',NULL),(563,NULL,'Verzorging: Consumptie',1,0,NULL,NULL,'A11',NULL),(564,NULL,'Nabehandeling',1,0,NULL,NULL,'A13',NULL),(565,NULL,'Artikel beeld',1,0,NULL,NULL,'A23',NULL),(566,NULL,'Hoofdkleur 1',1,0,NULL,NULL,'B01',NULL),(567,NULL,'Hoofdkleur 2',1,0,NULL,NULL,'B02',NULL),(568,NULL,'RHS hoofdkleur 1',1,0,NULL,NULL,'B03',NULL),(569,NULL,'RHS hoofdkleur 2',1,0,NULL,NULL,'B04',NULL),(570,NULL,'Hoofdkleur 1 blad',1,0,NULL,NULL,'B05',NULL),(571,NULL,'Hoofdkleur 2 blad',1,0,NULL,NULL,'B06',NULL),(572,NULL,'RHS hoofdkleur 1 blad',1,0,NULL,NULL,'B07',NULL),(573,NULL,'RHS hoofdkleur 2 blad',1,0,NULL,NULL,'B08',NULL),(574,NULL,'Botanisch beeld',1,0,NULL,NULL,'B09',NULL),(575,NULL,'Hoofdkleur bes/vrucht',1,0,NULL,NULL,'B10',NULL),(576,NULL,'RHS hoofdkleur bes/vrucht',1,0,NULL,NULL,'B11',NULL),(577,NULL,'UPOV hoofdkleur 1 bloem',1,0,NULL,NULL,'B12',NULL),(578,NULL,'UPOV hoofdkleur 2 bloem',1,0,NULL,NULL,'B13',NULL),(579,NULL,'UPOV hoofdkleur 1 blad',1,0,NULL,NULL,'B14',NULL),(580,NULL,'UPOV hoofdkleur 2 blad',1,0,NULL,NULL,'B15',NULL),(581,NULL,'UPOV hoofdkleur bes/vruch',1,0,NULL,NULL,'B16',NULL),(582,NULL,'Negatieve keurcode 1',1,0,NULL,NULL,'K01',NULL),(583,NULL,'Negatieve keurcode 2',1,0,NULL,NULL,'K02',NULL),(584,NULL,'Bedrijfskenmerk fytosanit',1,0,NULL,NULL,'K03',NULL),(585,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K04',NULL),(586,NULL,'Certificaten MPS-TraceCer',1,0,NULL,NULL,'K05',NULL),(587,NULL,'Overige leveranciersinfor',1,0,NULL,NULL,'K07',NULL),(588,NULL,'Certificaten MPS-GAP',1,0,NULL,NULL,'K08',NULL),(589,NULL,'Betrouwbaarheidsindex kla',1,0,NULL,NULL,'K11',NULL),(590,NULL,'Betrouwbaarheidsindex waa',1,0,NULL,NULL,'K12',NULL),(591,NULL,'Productkwaliteitslabel',1,0,NULL,NULL,'K13',NULL),(592,NULL,'Label Fair Flowers Fair P',1,0,NULL,NULL,'K14',NULL),(593,NULL,'Certificaten Socialy Qual',1,0,NULL,NULL,'K15',NULL),(594,NULL,'Certificaten GlobalGAP',1,0,NULL,NULL,'K16',NULL),(595,NULL,'Certificaten MPS Quality',1,0,NULL,NULL,'K17',NULL),(596,NULL,'Certificaten biologisch',1,0,NULL,NULL,'K18',NULL),(597,NULL,'Certificaten eetbare prod',1,0,NULL,NULL,'K19',NULL),(598,NULL,'Certificaten Florimark',1,0,NULL,NULL,'K20',NULL),(599,NULL,'Certificaten Milieukeur',1,0,NULL,NULL,'K21',NULL),(600,NULL,'Certificaten Kenya Flower',1,0,NULL,NULL,'K22',NULL),(601,NULL,'Certificaten Fairtrade',1,0,NULL,NULL,'K23',NULL),(602,NULL,'Keurmerk MPS-ProductProof',1,0,NULL,NULL,'K24',NULL),(603,NULL,'Certificaten ISO',1,0,NULL,NULL,'K25',NULL),(604,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K26',NULL),(605,NULL,'Certificaten Florverde',1,0,NULL,NULL,'K27',NULL),(606,NULL,'Certificaten Ethical Trad',1,0,NULL,NULL,'K28',NULL),(607,NULL,'Certificaten Ethiopian EH',1,0,NULL,NULL,'K29',NULL),(608,NULL,'Certificaten gewasbescher',1,0,NULL,NULL,'K30',NULL),(609,NULL,'Certificaten SAN',1,0,NULL,NULL,'K31',NULL),(610,NULL,'Certificaten GRASP',1,0,NULL,NULL,'K32',NULL),(611,NULL,'Label Fair Flora',1,0,NULL,NULL,'K33',NULL),(612,NULL,'GLobalG.A.P. Chain of Cus',1,0,NULL,NULL,'K34',NULL),(613,NULL,'Fust',1,0,NULL,NULL,'L01',NULL),(614,NULL,'Stapelwagen',1,0,NULL,NULL,'L02',NULL),(615,NULL,'Aantal legborden veilings',1,0,NULL,NULL,'L03',NULL),(616,NULL,'Aantal legborden Deense s',1,0,NULL,NULL,'L04',NULL),(617,NULL,'Aantal onderstellen Deens',1,0,NULL,NULL,'L05',NULL),(618,NULL,'Fustsoort',1,0,NULL,NULL,'L06',NULL),(619,NULL,'Fustmateriaal',1,0,NULL,NULL,'L07',NULL),(620,NULL,'Aantal legborden Eurostap',1,0,NULL,NULL,'L08',NULL),(621,NULL,'Aantal onderstellen Euros',1,0,NULL,NULL,'L09',NULL),(622,NULL,'Tallos/bolsa',1,0,NULL,NULL,'L11',''),(623,NULL,'Aantal bossen per bundel',1,0,NULL,NULL,'L12',NULL),(624,NULL,'Aantal stuks per fust',1,0,NULL,NULL,'L13',NULL),(625,NULL,'Aantal bossen per fust',1,0,NULL,NULL,'L14',NULL),(626,NULL,'Aantal bundels per fust',1,0,NULL,NULL,'L15',NULL),(627,NULL,'Aantal bossen per hoes',1,0,NULL,NULL,'L16',NULL),(628,NULL,'Aantal bundels per hoes',1,0,NULL,NULL,'L17',NULL),(629,NULL,'Fustlabel',1,0,NULL,NULL,'L18',NULL),(630,NULL,'Karlabel',1,0,NULL,NULL,'L19',NULL),(631,NULL,'Service productlabel',1,0,NULL,NULL,'L20',NULL),(632,NULL,'Service fustlabel',1,0,NULL,NULL,'L21',NULL),(633,NULL,'Service karlabel',1,0,NULL,NULL,'L22',NULL),(634,NULL,'Aantal fusten per laag',1,0,NULL,NULL,'L23',NULL),(635,NULL,'Presentatie per schapm2',1,0,NULL,NULL,'L24',NULL),(636,NULL,'Positieve keurcode fytosa',1,0,NULL,NULL,'P01',NULL),(637,NULL,'Positieve keurcode kwalit',1,0,NULL,NULL,'P02',NULL),(638,NULL,'Positieve keurcode veilin',1,0,NULL,NULL,'P03',NULL),(639,NULL,'Maceta',1,1,NULL,'cm','S01','diameter'),(640,NULL,'Altura',1,0,NULL,NULL,'S02','size'),(641,NULL,'nº plantas',1,0,NULL,NULL,'S03',NULL),(642,NULL,'Diámetro',1,0,NULL,NULL,'S04',NULL),(644,NULL,'Combinatiehoogte',1,0,NULL,NULL,'S06',NULL),(645,NULL,'Plantas/Maceta',1,0,NULL,NULL,'S07',NULL),(646,NULL,'Dikte',1,0,NULL,NULL,'S08',NULL),(647,NULL,'nº flores',1,0,NULL,NULL,'S09',NULL),(648,NULL,'Min aantal bloemtrossen p',1,0,NULL,NULL,'S10',NULL),(649,NULL,'nº ramales',1,0,NULL,NULL,'S11',NULL),(650,NULL,'Minimum aantal bollen per',1,0,NULL,NULL,'S12',NULL),(651,NULL,'Minimum aantal bladeren p',1,0,NULL,NULL,'S13',NULL),(652,NULL,'Minimum stamhoogte',1,0,NULL,NULL,'S14',NULL),(653,NULL,'Altura caja',1,0,NULL,NULL,'S15',NULL),(654,NULL,'Lengte scheuten',1,0,NULL,NULL,'S16',NULL),(655,NULL,'Min aant vertakkingen pr ',1,0,NULL,NULL,'S17',NULL),(656,NULL,'Altura del capullo',1,0,NULL,NULL,'S19',NULL),(658,NULL,'Peso tallo',1,0,NULL,NULL,'S21',NULL),(659,NULL,'nº flores',1,0,NULL,NULL,'S22',NULL),(660,NULL,'Diámetro de la flor',1,0,NULL,NULL,'S23',NULL),(661,NULL,'Minimum bloemschedelengte',1,0,NULL,NULL,'S24',NULL),(662,NULL,'Aantal bloemkoppen per tr',1,0,NULL,NULL,'S25',NULL),(663,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S26',NULL),(664,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S27',NULL),(665,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S28',NULL),(666,NULL,'Longitud inflorescencia',1,0,NULL,NULL,'S29',NULL),(667,NULL,'Verpakkingswijze snijbloe',1,0,NULL,NULL,'S30',NULL),(668,NULL,'Minimum aant bloemen per ',1,0,NULL,NULL,'S31',NULL),(669,NULL,'Longitud',1,0,NULL,NULL,'S32',NULL),(670,NULL,'Jaartal sortering hout',1,0,NULL,NULL,'S33',NULL),(671,NULL,'Diámetro de la hoja',1,0,NULL,NULL,'S34',NULL),(672,NULL,'Peso paquete',1,0,NULL,NULL,'S35',NULL),(673,NULL,'Maximum planthoogte',1,0,NULL,NULL,'S36',NULL),(674,NULL,'Maximum plantdiameter',1,0,NULL,NULL,'S37',NULL),(675,NULL,'Max aantal bloemen/bloeiw',1,0,NULL,NULL,'S38',NULL),(676,NULL,'Maximum aantal takken per',1,0,NULL,NULL,'S39',NULL),(677,NULL,'Maximum aantal bollen per',1,0,NULL,NULL,'S40',NULL),(678,NULL,'Maximum stamhoogte',1,0,NULL,NULL,'S41',NULL),(679,NULL,'Longitud mínima',1,0,NULL,NULL,'S42','size'),(680,NULL,'Maximum aantal knoppen sn',1,0,NULL,NULL,'S43',NULL),(681,NULL,'Maximum bloemdiameter',1,0,NULL,NULL,'S44',NULL),(682,NULL,'Maximum bloeiwijzelengte',1,0,NULL,NULL,'S45',NULL),(683,NULL,'Aantal vruchten / trossen',1,0,NULL,NULL,'S46',NULL),(684,NULL,'Verpakkingswijze',1,0,NULL,NULL,'S47',NULL),(685,NULL,'Minimum vruchtdiameter',1,0,NULL,NULL,'S48',NULL),(686,NULL,'Bolomvang',1,0,NULL,NULL,'S49',NULL),(687,NULL,'Bloem/bes/vruchtkleur 1',1,0,NULL,NULL,'S50',NULL),(688,NULL,'Potvorm',1,0,NULL,NULL,'S51',NULL),(689,NULL,'Potkleur',1,0,NULL,NULL,'S52',NULL),(690,NULL,'Material maceta',1,0,NULL,NULL,'S53',NULL),(691,NULL,'Plantvorm',1,0,NULL,NULL,'S54',NULL),(692,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S55',NULL),(693,NULL,'Teeltwijze',1,0,NULL,NULL,'S56',NULL),(694,NULL,'Teeltmedium',1,0,NULL,NULL,'S57',NULL),(695,NULL,'Hoesmateriaal',1,0,NULL,NULL,'S58',NULL),(696,NULL,'Hoesvorm',1,0,NULL,NULL,'S59',NULL),(697,NULL,'Hoesbedrukking algemeen',1,0,NULL,NULL,'S60',NULL),(698,NULL,'Extra toevoegingen',1,0,NULL,NULL,'S61',NULL),(699,NULL,'Land van herkomst (bedrij',1,0,NULL,NULL,'S62',NULL),(700,NULL,'Verpakte orchidee',1,0,NULL,NULL,'S63',NULL),(701,NULL,'Hoesbedrukking extra',1,0,NULL,NULL,'S64',NULL),(702,NULL,'Voorbehandeling',1,0,NULL,NULL,'S65',NULL),(703,NULL,'Overige niet in pot',1,0,NULL,NULL,'S66',NULL),(704,NULL,'Vorm snijbloemen',1,0,NULL,NULL,'S67',NULL),(705,NULL,'Buigzaamheid bloemsteel',1,0,NULL,NULL,'S68',NULL),(706,NULL,'Hoeskleur',1,0,NULL,NULL,'S69',NULL),(707,NULL,'Extra deco materiaal',1,0,NULL,NULL,'S70',NULL),(708,NULL,'Productkleur',1,0,NULL,NULL,'S71','inkFk'),(709,NULL,'Productmateriaal',1,0,NULL,NULL,'S72',NULL),(710,NULL,'Materiaalhoogte',1,0,NULL,NULL,'S73',NULL),(711,NULL,'Materiaaldiameter',1,0,NULL,NULL,'S74',NULL),(712,NULL,'Barcode',1,0,NULL,NULL,'S75',NULL),(713,NULL,'Productlabel',1,0,NULL,NULL,'S76',NULL),(714,NULL,'Eetbaar/ niet eetbaar',1,0,NULL,NULL,'S77',NULL),(715,NULL,'Plantmaat zonder pot',1,0,NULL,NULL,'S78',NULL),(716,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S79',NULL),(717,NULL,'Maximum percentage oud ho',1,0,NULL,NULL,'S80',NULL),(718,NULL,'Maximum lengte verschil',1,0,NULL,NULL,'S81',NULL),(719,NULL,'Bladkleur',1,0,NULL,NULL,'S82',NULL),(720,NULL,'Plantgewicht',1,0,NULL,NULL,'S83',NULL),(721,NULL,'Diámetro',1,0,NULL,NULL,'S84',NULL),(722,NULL,'Bloem/bes/vruchtkleur 2',1,0,NULL,NULL,'S85',NULL),(723,NULL,'Winterhardheid (USDA zone',1,0,NULL,NULL,'S86',NULL),(724,NULL,'Kleurbehandeld',1,0,NULL,NULL,'S87','inkFk'),(725,NULL,'Bloem-/bladkleurverdeling',1,0,NULL,NULL,'S88',NULL),(726,NULL,'Diámetro del capullo',1,0,NULL,NULL,'S89',NULL),(727,NULL,'Volume inhoud',1,0,NULL,NULL,'S90',NULL),(728,NULL,'Vruchtbenaming',1,0,NULL,NULL,'S91',NULL),(729,NULL,'Vaaslevenindex',1,0,NULL,NULL,'S92',NULL),(730,NULL,'Overige informatie plante',1,0,NULL,NULL,'S93',NULL),(731,NULL,'Overige informatie snijbl',1,0,NULL,NULL,'S94',NULL),(732,NULL,'Toepassingsmogelijkheid',1,0,NULL,NULL,'S95',NULL),(733,NULL,'Productbeeld aanvoerder',1,0,NULL,NULL,'S96',NULL),(734,NULL,'MPS certificering',1,0,NULL,NULL,'S97',NULL),(735,NULL,'Kwaliteitsgroep',1,0,NULL,NULL,'S98',NULL),(736,NULL,'Artikelomschrijving',1,0,NULL,NULL,'S99',NULL),(737,NULL,'BTW-tarief',1,0,NULL,NULL,'T01',NULL),(738,NULL,'Prijseenheid',1,0,NULL,NULL,'T02',NULL),(739,NULL,'Transactievorm',1,0,NULL,NULL,'T03',NULL),(740,NULL,'Handelsverpakking voorwaa',1,0,NULL,NULL,'T10',NULL),(741,NULL,'Consumentenverpakking voo',1,0,NULL,NULL,'T11',NULL),(742,NULL,'Leveringsvoorwaarden',1,0,NULL,NULL,'T12',NULL),(743,NULL,'PT heffing voorwaarden',1,0,NULL,NULL,'T13',NULL),(744,NULL,'Serviceheffing voorwaarde',1,0,NULL,NULL,'T14',NULL),(745,NULL,'Algemene voorwaarden',1,0,NULL,NULL,'T15',NULL),(746,NULL,'Marktvorm',1,0,NULL,NULL,'T16',NULL),(747,NULL,'Themadagen',1,0,NULL,NULL,'T17',NULL),(748,NULL,'Handelscategorie',1,0,NULL,NULL,'T18',NULL),(749,NULL,'Producentengroepen',1,0,NULL,NULL,'T19',NULL),(750,NULL,'Favorieten Id',1,0,NULL,NULL,'T20',NULL),(751,NULL,'Verkoopeenheid',1,0,NULL,NULL,'T21',NULL),(752,NULL,'Veilgroep voorkeur',1,0,NULL,NULL,'V01',NULL),(753,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V02',NULL),(754,NULL,'Keurmeesternummer FloraHo',1,0,NULL,NULL,'V03',NULL),(755,NULL,'Rijnummer Rijnsburg',1,0,NULL,NULL,'V04',NULL),(756,NULL,'Verwerkingslocatie FloraH',1,0,NULL,NULL,'V05',NULL),(757,NULL,'FloraHolland Financial',1,0,NULL,NULL,'V06',NULL),(758,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V07',NULL),(759,NULL,'Benefiet veiling',1,0,NULL,NULL,'V08',NULL),(760,NULL,'Kloksoort',1,0,NULL,NULL,'V09',NULL),(761,NULL,'Minimumprijs aanvoerder',1,0,NULL,NULL,'V10',NULL),(762,NULL,'Rest aantallen',1,0,NULL,NULL,'V11',NULL),(763,NULL,'Veilsoort',1,0,NULL,NULL,'V12',NULL),(764,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V13',NULL),(765,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V14',NULL),(766,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V15',NULL),(767,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V16',NULL),(768,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V17',NULL),(769,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V18',NULL),(770,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V19',NULL),(771,NULL,'Gereserveerd',1,0,NULL,NULL,'V20',NULL),(772,NULL,'Veilgroep Aalsmeer',1,0,NULL,NULL,'V21',NULL),(773,NULL,'Promotie kenmerk FloraHol',1,0,NULL,NULL,'V22',NULL),(774,NULL,'Verrekening snijbloemenvo',1,0,NULL,NULL,'V23',NULL),(775,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V24',NULL),(776,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V25',NULL),(777,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V26',NULL),(778,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V27',NULL),(779,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V28',NULL),(780,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V29',NULL),(781,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V30',NULL),(782,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V31',NULL),(783,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V32',NULL),(784,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V33',NULL),(785,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V34',NULL),(786,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V35',NULL),(787,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V36',NULL),(788,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V37',NULL),(789,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V38',NULL),(790,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V39',NULL),(791,NULL,'Gereserveerd',1,0,NULL,NULL,'V40',NULL),(792,NULL,'Tussenopslag klok Plantio',1,0,NULL,NULL,'V41',NULL),(793,NULL,'Soort ladingsdrager Plant',1,0,NULL,NULL,'V42',NULL),(794,NULL,'Logistiek middel Plantion',1,0,NULL,NULL,'V43',NULL),(795,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V44',NULL),(796,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V45',NULL),(797,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V46',NULL),(798,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V47',NULL),(799,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V48',NULL),(800,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V49',NULL),(801,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V50',NULL),(802,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V51',NULL),(803,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V52',NULL),(804,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V53',NULL),(805,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V54',NULL),(806,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V55',NULL),(807,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V56',NULL),(808,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V57',NULL),(809,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V58',NULL),(810,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V59',NULL),(811,NULL,'Gereserveerd',1,0,NULL,NULL,'V60',NULL),(812,NULL,'Veilgroep Plantion Ede',1,0,NULL,NULL,'V61',NULL),(813,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V62',NULL),(814,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V63',NULL),(815,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V64',NULL),(816,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V65',NULL),(817,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V66',NULL),(818,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V67',NULL),(819,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V68',NULL),(820,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V69',NULL),(821,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V70',NULL),(822,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V71',NULL),(823,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V72',NULL),(824,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V73',NULL),(825,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V74',NULL),(826,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V75',NULL),(827,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V76',NULL),(828,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V77',NULL),(829,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V78',NULL),(830,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V79',NULL),(831,NULL,'Toegevoegde waardes VRM',1,0,NULL,NULL,'V80',NULL),(832,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V81',NULL),(833,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V82',NULL),(834,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V83',NULL),(835,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V84',NULL),(836,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V85',NULL),(837,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V86',NULL),(838,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V87',NULL),(839,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V88',NULL),(840,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V89',NULL),(841,NULL,'Veiling',1,0,NULL,NULL,'V99',NULL),(842,NULL,'kopersaantallen',1,0,NULL,NULL,'Z01',NULL),(843,NULL,'Caducidad',1,0,NULL,NULL,NULL,NULL); +INSERT INTO `tag` VALUES (1,'color','Color',0,0,'ink',NULL,NULL,'inkFk'),(2,NULL,'Forma',1,0,NULL,NULL,NULL,NULL),(3,NULL,'Material',1,0,NULL,NULL,NULL,NULL),(4,NULL,'Longitud',1,1,NULL,'mm',NULL,'size'),(5,NULL,'Diámetro',1,1,NULL,'mm',NULL,'diameter'),(6,NULL,'Perímetro',1,1,NULL,'mm',NULL,NULL),(7,NULL,'Ancho de la base',1,1,NULL,'mm',NULL,NULL),(8,NULL,'Altura',1,1,NULL,'mm',NULL,'size'),(9,NULL,'Volumen',1,1,NULL,'ml',NULL,NULL),(10,NULL,'Densidad',1,1,NULL,NULL,NULL,NULL),(11,NULL,'Calidad',1,0,NULL,NULL,NULL,NULL),(12,NULL,'Textura',1,0,NULL,NULL,NULL,NULL),(13,NULL,'Material del mango',1,0,NULL,NULL,NULL,NULL),(14,NULL,'Compra mínima',1,0,NULL,NULL,NULL,NULL),(15,NULL,'Nº pétalos',1,1,NULL,NULL,NULL,NULL),(16,NULL,'Ancho',1,1,NULL,'mm',NULL,NULL),(18,NULL,'Profundidad',1,1,NULL,'mm',NULL,NULL),(19,NULL,'Largo',1,1,NULL,'mm',NULL,'size'),(20,NULL,'Ancho superior',1,1,NULL,'mm',NULL,NULL),(21,NULL,'Ancho inferior',1,1,NULL,'mm',NULL,NULL),(22,NULL,'Gramaje',1,1,NULL,'g',NULL,NULL),(23,'stems','Tallos',1,1,NULL,NULL,NULL,'stems'),(24,NULL,'Estado',1,0,NULL,NULL,NULL,NULL),(25,NULL,'Color principal',0,0,'ink',NULL,NULL,NULL),(26,NULL,'Color secundario',0,0,'ink',NULL,NULL,NULL),(27,NULL,'Longitud(cm)',1,1,NULL,'cm',NULL,NULL),(28,NULL,'Diámetro base',1,1,'','mm',NULL,'diameter'),(29,NULL,'Colección',1,0,NULL,NULL,NULL,NULL),(30,NULL,'Uds / caja',1,1,NULL,NULL,NULL,NULL),(31,NULL,'Contenido',1,0,NULL,NULL,NULL,NULL),(32,NULL,'Peso',1,1,NULL,'g',NULL,NULL),(33,NULL,'Grosor',1,1,NULL,'mm',NULL,NULL),(34,NULL,'Marca',1,0,NULL,NULL,NULL,NULL),(35,'origin','Origen',0,0,'origin',NULL,NULL,'originFk'),(36,NULL,'Proveedor',1,0,NULL,NULL,NULL,NULL),(37,'producer','Productor',0,0,'producer',NULL,NULL,'producerFk'),(38,NULL,'Duración',1,1,NULL,'s',NULL,NULL),(39,NULL,'Flor',1,0,NULL,NULL,NULL,NULL),(40,NULL,'Soporte',1,0,NULL,NULL,NULL,NULL),(41,NULL,'Tamaño flor',1,0,NULL,NULL,NULL,NULL),(42,NULL,'Apertura',1,0,NULL,NULL,NULL,NULL),(43,NULL,'Tallo',1,0,NULL,NULL,NULL,NULL),(44,NULL,'Nº hojas',1,1,NULL,NULL,NULL,NULL),(45,NULL,'Dimensiones',1,0,NULL,NULL,NULL,NULL),(46,NULL,'Diámetro boca',1,1,NULL,'mm',NULL,NULL),(47,NULL,'Nº flores',1,1,NULL,NULL,NULL,NULL),(48,NULL,'Uds / paquete',1,1,NULL,NULL,NULL,NULL),(49,NULL,'Maceta',1,1,NULL,'cm',NULL,'diameter'),(50,NULL,'Textura flor',1,0,NULL,NULL,NULL,NULL),(51,NULL,'Textura hoja',1,0,NULL,NULL,NULL,NULL),(52,NULL,'Tipo de IVA',1,0,NULL,NULL,NULL,NULL),(53,NULL,'Tronco',1,0,NULL,NULL,NULL,NULL),(54,NULL,'Hoja',1,0,NULL,NULL,NULL,NULL),(55,NULL,'Formato',1,0,NULL,NULL,NULL,NULL),(56,NULL,'Genero',1,0,NULL,NULL,NULL,NULL),(57,NULL,'Especie',1,0,NULL,NULL,NULL,NULL),(58,NULL,'Variedad',1,0,NULL,NULL,NULL,NULL),(59,NULL,'Medida grande',1,0,NULL,NULL,NULL,NULL),(60,NULL,'Medida mediano',1,0,NULL,NULL,NULL,NULL),(61,NULL,'Medida pequeño',1,0,NULL,NULL,NULL,NULL),(63,NULL,'Recipiente interior',1,0,NULL,NULL,NULL,NULL),(64,NULL,'Material secundario',1,0,NULL,NULL,NULL,NULL),(65,NULL,'Colores',1,0,NULL,NULL,NULL,NULL),(66,NULL,'Referencia',1,0,NULL,NULL,NULL,NULL),(67,'category','Categoria',1,0,NULL,NULL,NULL,NULL),(68,NULL,'Amb',1,0,NULL,NULL,NULL,NULL),(69,NULL,'Anchura',1,1,NULL,'cm',NULL,NULL),(70,NULL,'Hueco interior',1,0,NULL,NULL,NULL,NULL),(71,NULL,'Tamaño',1,0,NULL,NULL,NULL,NULL),(72,NULL,'Color botón',1,0,NULL,NULL,NULL,NULL),(73,NULL,'Tamaño minimo del botón',1,0,NULL,NULL,NULL,NULL),(74,NULL,'Obtentor',1,0,NULL,NULL,NULL,NULL),(75,NULL,'Longitud del brote',1,0,NULL,NULL,NULL,NULL),(76,NULL,'Tallos / u.v.',1,0,NULL,NULL,NULL,NULL),(77,NULL,'Madera de',1,0,NULL,NULL,NULL,NULL),(78,NULL,'Unidad de venta',1,0,NULL,NULL,NULL,NULL),(79,NULL,'Temporal',1,0,NULL,NULL,NULL,NULL),(80,NULL,'Gramaje/tallo',1,1,NULL,'g',NULL,NULL),(81,NULL,'Peso/paquete',1,1,NULL,'g',NULL,NULL),(82,NULL,'Flexibilidad del tallo',1,0,NULL,NULL,NULL,NULL),(83,NULL,'Nº planchas',1,1,NULL,NULL,NULL,NULL),(84,NULL,'Nº páginas',1,1,NULL,NULL,NULL,NULL),(85,NULL,'Editorial',1,0,NULL,NULL,NULL,NULL),(86,NULL,'Idioma',1,0,NULL,NULL,NULL,NULL),(87,NULL,'Fecha publicación',1,0,NULL,NULL,NULL,NULL),(88,NULL,'Cubierta',1,0,NULL,NULL,NULL,NULL),(89,NULL,'Encuadernación',1,0,NULL,NULL,NULL,NULL),(90,NULL,'Autor',1,0,NULL,NULL,NULL,NULL),(91,NULL,'Envoltorio',1,0,NULL,NULL,NULL,NULL),(92,NULL,'Nombre temporal',1,0,NULL,NULL,NULL,NULL),(93,NULL,'Modelo',1,0,NULL,NULL,NULL,NULL),(94,NULL,'Producto',1,0,NULL,NULL,NULL,NULL),(95,NULL,'Título',1,0,NULL,NULL,NULL,NULL),(96,NULL,'Tomo',1,0,NULL,NULL,NULL,NULL),(97,NULL,'Articulo',1,0,NULL,NULL,NULL,NULL),(98,NULL,'Metodo de cultivo',1,0,NULL,NULL,NULL,NULL),(99,NULL,'Edad',1,0,NULL,NULL,NULL,NULL),(100,NULL,'Agotado',1,0,NULL,NULL,NULL,NULL),(101,NULL,'Altura con asa',1,1,NULL,'cm',NULL,NULL),(102,NULL,'Nº tallos',1,1,NULL,NULL,NULL,NULL),(103,NULL,'Cultivo',1,0,NULL,NULL,NULL,NULL),(104,NULL,'Sabor',1,0,NULL,NULL,NULL,NULL),(105,NULL,'Talla',1,0,NULL,NULL,NULL,NULL),(106,NULL,'Calibre',1,1,NULL,NULL,NULL,NULL),(107,NULL,'Dulzura',1,1,NULL,'bx',NULL,NULL),(108,NULL,'Piezas',1,0,NULL,NULL,NULL,NULL),(109,NULL,'Altura con patas',1,0,NULL,NULL,NULL,NULL),(110,NULL,'Envase',1,0,NULL,NULL,NULL,NULL),(111,NULL,'Nº piezas',1,0,NULL,NULL,NULL,NULL),(112,NULL,'Uso',1,0,NULL,'cm',NULL,NULL),(113,NULL,'Color luz',1,0,NULL,NULL,NULL,NULL),(114,NULL,'Capacidad',1,0,NULL,NULL,NULL,NULL),(184,NULL,'Tallos por paquete',1,0,NULL,NULL,NULL,NULL),(205,NULL,'Apertura',1,0,NULL,NULL,'S05',NULL),(219,NULL,'Altura',1,0,NULL,NULL,'S20','size'),(552,NULL,'fout kenmerk',1,0,NULL,NULL,'081',NULL),(553,NULL,'Potinhoud',1,0,NULL,NULL,'A01',NULL),(554,NULL,'Marketingconcept',1,0,NULL,NULL,'A02',NULL),(555,NULL,'Leeftijd',1,0,NULL,NULL,'A03',NULL),(556,NULL,'Uitgangsmateriaal',1,0,NULL,NULL,'A04',NULL),(557,NULL,'Kleurbehandeld',1,0,NULL,NULL,'A05','inkFk'),(558,NULL,'Verzorging: Standplaats',1,0,NULL,NULL,'A06',NULL),(559,NULL,'Verzorging: Water',1,0,NULL,NULL,'A07',NULL),(560,NULL,'Verzorging: Voeding',1,0,NULL,NULL,'A08',NULL),(561,NULL,'Verzorging: Temperatuur',1,0,NULL,NULL,'A09',NULL),(562,NULL,'Verzorging: Specifieke in',1,0,NULL,NULL,'A10',NULL),(563,NULL,'Verzorging: Consumptie',1,0,NULL,NULL,'A11',NULL),(564,NULL,'Nabehandeling',1,0,NULL,NULL,'A13',NULL),(565,NULL,'Artikel beeld',1,0,NULL,NULL,'A23',NULL),(566,NULL,'Hoofdkleur 1',1,0,NULL,NULL,'B01',NULL),(567,NULL,'Hoofdkleur 2',1,0,NULL,NULL,'B02',NULL),(568,NULL,'RHS hoofdkleur 1',1,0,NULL,NULL,'B03',NULL),(569,NULL,'RHS hoofdkleur 2',1,0,NULL,NULL,'B04',NULL),(570,NULL,'Hoofdkleur 1 blad',1,0,NULL,NULL,'B05',NULL),(571,NULL,'Hoofdkleur 2 blad',1,0,NULL,NULL,'B06',NULL),(572,NULL,'RHS hoofdkleur 1 blad',1,0,NULL,NULL,'B07',NULL),(573,NULL,'RHS hoofdkleur 2 blad',1,0,NULL,NULL,'B08',NULL),(574,NULL,'Botanisch beeld',1,0,NULL,NULL,'B09',NULL),(575,NULL,'Hoofdkleur bes/vrucht',1,0,NULL,NULL,'B10',NULL),(576,NULL,'RHS hoofdkleur bes/vrucht',1,0,NULL,NULL,'B11',NULL),(577,NULL,'UPOV hoofdkleur 1 bloem',1,0,NULL,NULL,'B12',NULL),(578,NULL,'UPOV hoofdkleur 2 bloem',1,0,NULL,NULL,'B13',NULL),(579,NULL,'UPOV hoofdkleur 1 blad',1,0,NULL,NULL,'B14',NULL),(580,NULL,'UPOV hoofdkleur 2 blad',1,0,NULL,NULL,'B15',NULL),(581,NULL,'UPOV hoofdkleur bes/vruch',1,0,NULL,NULL,'B16',NULL),(582,NULL,'Negatieve keurcode 1',1,0,NULL,NULL,'K01',NULL),(583,NULL,'Negatieve keurcode 2',1,0,NULL,NULL,'K02',NULL),(584,NULL,'Bedrijfskenmerk fytosanit',1,0,NULL,NULL,'K03',NULL),(585,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K04',NULL),(586,NULL,'Certificaten MPS-TraceCer',1,0,NULL,NULL,'K05',NULL),(587,NULL,'Overige leveranciersinfor',1,0,NULL,NULL,'K07',NULL),(588,NULL,'Certificaten MPS-GAP',1,0,NULL,NULL,'K08',NULL),(589,NULL,'Betrouwbaarheidsindex kla',1,0,NULL,NULL,'K11',NULL),(590,NULL,'Betrouwbaarheidsindex waa',1,0,NULL,NULL,'K12',NULL),(591,NULL,'Productkwaliteitslabel',1,0,NULL,NULL,'K13',NULL),(592,NULL,'Label Fair Flowers Fair P',1,0,NULL,NULL,'K14',NULL),(593,NULL,'Certificaten Socialy Qual',1,0,NULL,NULL,'K15',NULL),(594,NULL,'Certificaten GlobalGAP',1,0,NULL,NULL,'K16',NULL),(595,NULL,'Certificaten MPS Quality',1,0,NULL,NULL,'K17',NULL),(596,NULL,'Certificaten biologisch',1,0,NULL,NULL,'K18',NULL),(597,NULL,'Certificaten eetbare prod',1,0,NULL,NULL,'K19',NULL),(598,NULL,'Certificaten Florimark',1,0,NULL,NULL,'K20',NULL),(599,NULL,'Certificaten Milieukeur',1,0,NULL,NULL,'K21',NULL),(600,NULL,'Certificaten Kenya Flower',1,0,NULL,NULL,'K22',NULL),(601,NULL,'Certificaten Fairtrade',1,0,NULL,NULL,'K23',NULL),(602,NULL,'Keurmerk MPS-ProductProof',1,0,NULL,NULL,'K24',NULL),(603,NULL,'Certificaten ISO',1,0,NULL,NULL,'K25',NULL),(604,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K26',NULL),(605,NULL,'Certificaten Florverde',1,0,NULL,NULL,'K27',NULL),(606,NULL,'Certificaten Ethical Trad',1,0,NULL,NULL,'K28',NULL),(607,NULL,'Certificaten Ethiopian EH',1,0,NULL,NULL,'K29',NULL),(608,NULL,'Certificaten gewasbescher',1,0,NULL,NULL,'K30',NULL),(609,NULL,'Certificaten SAN',1,0,NULL,NULL,'K31',NULL),(610,NULL,'Certificaten GRASP',1,0,NULL,NULL,'K32',NULL),(611,NULL,'Label Fair Flora',1,0,NULL,NULL,'K33',NULL),(612,NULL,'GLobalG.A.P. Chain of Cus',1,0,NULL,NULL,'K34',NULL),(613,NULL,'Fust',1,0,NULL,NULL,'L01',NULL),(614,NULL,'Stapelwagen',1,0,NULL,NULL,'L02',NULL),(615,NULL,'Aantal legborden veilings',1,0,NULL,NULL,'L03',NULL),(616,NULL,'Aantal legborden Deense s',1,0,NULL,NULL,'L04',NULL),(617,NULL,'Aantal onderstellen Deens',1,0,NULL,NULL,'L05',NULL),(618,NULL,'Fustsoort',1,0,NULL,NULL,'L06',NULL),(619,NULL,'Fustmateriaal',1,0,NULL,NULL,'L07',NULL),(620,NULL,'Aantal legborden Eurostap',1,0,NULL,NULL,'L08',NULL),(621,NULL,'Aantal onderstellen Euros',1,0,NULL,NULL,'L09',NULL),(622,NULL,'Tallos/bolsa',1,0,NULL,NULL,'L11',''),(623,NULL,'Aantal bossen per bundel',1,0,NULL,NULL,'L12',NULL),(624,NULL,'Aantal stuks per fust',1,0,NULL,NULL,'L13',NULL),(625,NULL,'Aantal bossen per fust',1,0,NULL,NULL,'L14',NULL),(626,NULL,'Aantal bundels per fust',1,0,NULL,NULL,'L15',NULL),(627,NULL,'Aantal bossen per hoes',1,0,NULL,NULL,'L16',NULL),(628,NULL,'Aantal bundels per hoes',1,0,NULL,NULL,'L17',NULL),(629,NULL,'Fustlabel',1,0,NULL,NULL,'L18',NULL),(630,NULL,'Karlabel',1,0,NULL,NULL,'L19',NULL),(631,NULL,'Service productlabel',1,0,NULL,NULL,'L20',NULL),(632,NULL,'Service fustlabel',1,0,NULL,NULL,'L21',NULL),(633,NULL,'Service karlabel',1,0,NULL,NULL,'L22',NULL),(634,NULL,'Aantal fusten per laag',1,0,NULL,NULL,'L23',NULL),(635,NULL,'Presentatie per schapm2',1,0,NULL,NULL,'L24',NULL),(636,NULL,'Positieve keurcode fytosa',1,0,NULL,NULL,'P01',NULL),(637,NULL,'Positieve keurcode kwalit',1,0,NULL,NULL,'P02',NULL),(638,NULL,'Positieve keurcode veilin',1,0,NULL,NULL,'P03',NULL),(639,NULL,'Maceta',1,1,NULL,'cm','S01','diameter'),(640,NULL,'Altura',1,0,NULL,NULL,'S02','size'),(641,NULL,'nº plantas',1,0,NULL,NULL,'S03',NULL),(642,NULL,'Diámetro',1,0,NULL,NULL,'S04',NULL),(644,NULL,'Combinatiehoogte',1,0,NULL,NULL,'S06',NULL),(645,NULL,'Plantas/Maceta',1,0,NULL,NULL,'S07',NULL),(646,NULL,'Dikte',1,0,NULL,NULL,'S08',NULL),(647,NULL,'nº flores',1,0,NULL,NULL,'S09',NULL),(648,NULL,'Min aantal bloemtrossen p',1,0,NULL,NULL,'S10',NULL),(649,NULL,'nº ramales',1,0,NULL,NULL,'S11',NULL),(650,NULL,'Minimum aantal bollen per',1,0,NULL,NULL,'S12',NULL),(651,NULL,'Minimum aantal bladeren p',1,0,NULL,NULL,'S13',NULL),(652,NULL,'Minimum stamhoogte',1,0,NULL,NULL,'S14',NULL),(653,NULL,'Altura caja',1,0,NULL,NULL,'S15',NULL),(654,NULL,'Lengte scheuten',1,0,NULL,NULL,'S16',NULL),(655,NULL,'Min aant vertakkingen pr ',1,0,NULL,NULL,'S17',NULL),(656,NULL,'Altura del capullo',1,0,NULL,NULL,'S19',NULL),(658,NULL,'Peso tallo',1,0,NULL,NULL,'S21',NULL),(659,NULL,'nº flores',1,0,NULL,NULL,'S22',NULL),(660,NULL,'Diámetro de la flor',1,0,NULL,NULL,'S23',NULL),(661,NULL,'Minimum bloemschedelengte',1,0,NULL,NULL,'S24',NULL),(662,NULL,'Aantal bloemkoppen per tr',1,0,NULL,NULL,'S25',NULL),(663,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S26',NULL),(664,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S27',NULL),(665,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S28',NULL),(666,NULL,'Longitud inflorescencia',1,0,NULL,NULL,'S29',NULL),(667,NULL,'Verpakkingswijze snijbloe',1,0,NULL,NULL,'S30',NULL),(668,NULL,'Minimum aant bloemen per ',1,0,NULL,NULL,'S31',NULL),(669,NULL,'Longitud',1,0,NULL,NULL,'S32',NULL),(670,NULL,'Jaartal sortering hout',1,0,NULL,NULL,'S33',NULL),(671,NULL,'Diámetro de la hoja',1,0,NULL,NULL,'S34',NULL),(672,NULL,'Peso paquete',1,0,NULL,NULL,'S35',NULL),(673,NULL,'Maximum planthoogte',1,0,NULL,NULL,'S36',NULL),(674,NULL,'Maximum plantdiameter',1,0,NULL,NULL,'S37',NULL),(675,NULL,'Max aantal bloemen/bloeiw',1,0,NULL,NULL,'S38',NULL),(676,NULL,'Maximum aantal takken per',1,0,NULL,NULL,'S39',NULL),(677,NULL,'Maximum aantal bollen per',1,0,NULL,NULL,'S40',NULL),(678,NULL,'Maximum stamhoogte',1,0,NULL,NULL,'S41',NULL),(679,NULL,'Longitud mínima',1,0,NULL,NULL,'S42','size'),(680,NULL,'Maximum aantal knoppen sn',1,0,NULL,NULL,'S43',NULL),(681,NULL,'Maximum bloemdiameter',1,0,NULL,NULL,'S44',NULL),(682,NULL,'Maximum bloeiwijzelengte',1,0,NULL,NULL,'S45',NULL),(683,NULL,'Aantal vruchten / trossen',1,0,NULL,NULL,'S46',NULL),(684,NULL,'Verpakkingswijze',1,0,NULL,NULL,'S47',NULL),(685,NULL,'Minimum vruchtdiameter',1,0,NULL,NULL,'S48',NULL),(686,NULL,'Bolomvang',1,0,NULL,NULL,'S49',NULL),(687,NULL,'Bloem/bes/vruchtkleur 1',1,0,NULL,NULL,'S50',NULL),(688,NULL,'Potvorm',1,0,NULL,NULL,'S51',NULL),(689,NULL,'Potkleur',1,0,NULL,NULL,'S52',NULL),(690,NULL,'Material maceta',1,0,NULL,NULL,'S53',NULL),(691,NULL,'Plantvorm',1,0,NULL,NULL,'S54',NULL),(692,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S55',NULL),(693,NULL,'Teeltwijze',1,0,NULL,NULL,'S56',NULL),(694,NULL,'Teeltmedium',1,0,NULL,NULL,'S57',NULL),(695,NULL,'Hoesmateriaal',1,0,NULL,NULL,'S58',NULL),(696,NULL,'Hoesvorm',1,0,NULL,NULL,'S59',NULL),(697,NULL,'Hoesbedrukking algemeen',1,0,NULL,NULL,'S60',NULL),(698,NULL,'Extra toevoegingen',1,0,NULL,NULL,'S61',NULL),(699,NULL,'Land van herkomst (bedrij',1,0,NULL,NULL,'S62',NULL),(700,NULL,'Verpakte orchidee',1,0,NULL,NULL,'S63',NULL),(701,NULL,'Hoesbedrukking extra',1,0,NULL,NULL,'S64',NULL),(702,NULL,'Voorbehandeling',1,0,NULL,NULL,'S65',NULL),(703,NULL,'Overige niet in pot',1,0,NULL,NULL,'S66',NULL),(704,NULL,'Vorm snijbloemen',1,0,NULL,NULL,'S67',NULL),(705,NULL,'Buigzaamheid bloemsteel',1,0,NULL,NULL,'S68',NULL),(706,NULL,'Hoeskleur',1,0,NULL,NULL,'S69',NULL),(707,NULL,'Extra deco materiaal',1,0,NULL,NULL,'S70',NULL),(708,NULL,'Productkleur',1,0,NULL,NULL,'S71','inkFk'),(709,NULL,'Productmateriaal',1,0,NULL,NULL,'S72',NULL),(710,NULL,'Materiaalhoogte',1,0,NULL,NULL,'S73',NULL),(711,NULL,'Materiaaldiameter',1,0,NULL,NULL,'S74',NULL),(712,NULL,'Barcode',1,0,NULL,NULL,'S75',NULL),(713,NULL,'Productlabel',1,0,NULL,NULL,'S76',NULL),(714,NULL,'Eetbaar/ niet eetbaar',1,0,NULL,NULL,'S77',NULL),(715,NULL,'Plantmaat zonder pot',1,0,NULL,NULL,'S78',NULL),(716,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S79',NULL),(717,NULL,'Maximum percentage oud ho',1,0,NULL,NULL,'S80',NULL),(718,NULL,'Maximum lengte verschil',1,0,NULL,NULL,'S81',NULL),(719,NULL,'Bladkleur',1,0,NULL,NULL,'S82',NULL),(720,NULL,'Plantgewicht',1,0,NULL,NULL,'S83',NULL),(721,NULL,'Diámetro',1,0,NULL,NULL,'S84',NULL),(722,NULL,'Bloem/bes/vruchtkleur 2',1,0,NULL,NULL,'S85',NULL),(723,NULL,'Winterhardheid (USDA zone',1,0,NULL,NULL,'S86',NULL),(724,NULL,'Kleurbehandeld',1,0,NULL,NULL,'S87','inkFk'),(725,NULL,'Bloem-/bladkleurverdeling',1,0,NULL,NULL,'S88',NULL),(726,NULL,'Diámetro del capullo',1,0,NULL,NULL,'S89',NULL),(727,NULL,'Volume inhoud',1,0,NULL,NULL,'S90',NULL),(728,NULL,'Vruchtbenaming',1,0,NULL,NULL,'S91',NULL),(729,NULL,'Vaaslevenindex',1,0,NULL,NULL,'S92',NULL),(730,NULL,'Overige informatie plante',1,0,NULL,NULL,'S93',NULL),(731,NULL,'Overige informatie snijbl',1,0,NULL,NULL,'S94',NULL),(732,NULL,'Toepassingsmogelijkheid',1,0,NULL,NULL,'S95',NULL),(733,NULL,'Productbeeld aanvoerder',1,0,NULL,NULL,'S96',NULL),(734,NULL,'MPS certificering',1,0,NULL,NULL,'S97',NULL),(735,NULL,'Kwaliteitsgroep',1,0,NULL,NULL,'S98',NULL),(736,NULL,'Artikelomschrijving',1,0,NULL,NULL,'S99',NULL),(737,NULL,'BTW-tarief',1,0,NULL,NULL,'T01',NULL),(738,NULL,'Prijseenheid',1,0,NULL,NULL,'T02',NULL),(739,NULL,'Transactievorm',1,0,NULL,NULL,'T03',NULL),(740,NULL,'Handelsverpakking voorwaa',1,0,NULL,NULL,'T10',NULL),(741,NULL,'Consumentenverpakking voo',1,0,NULL,NULL,'T11',NULL),(742,NULL,'Leveringsvoorwaarden',1,0,NULL,NULL,'T12',NULL),(743,NULL,'PT heffing voorwaarden',1,0,NULL,NULL,'T13',NULL),(744,NULL,'Serviceheffing voorwaarde',1,0,NULL,NULL,'T14',NULL),(745,NULL,'Algemene voorwaarden',1,0,NULL,NULL,'T15',NULL),(746,NULL,'Marktvorm',1,0,NULL,NULL,'T16',NULL),(747,NULL,'Themadagen',1,0,NULL,NULL,'T17',NULL),(748,NULL,'Handelscategorie',1,0,NULL,NULL,'T18',NULL),(749,NULL,'Producentengroepen',1,0,NULL,NULL,'T19',NULL),(750,NULL,'Favorieten Id',1,0,NULL,NULL,'T20',NULL),(751,NULL,'Verkoopeenheid',1,0,NULL,NULL,'T21',NULL),(752,NULL,'Veilgroep voorkeur',1,0,NULL,NULL,'V01',NULL),(753,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V02',NULL),(754,NULL,'Keurmeesternummer FloraHo',1,0,NULL,NULL,'V03',NULL),(755,NULL,'Rijnummer Rijnsburg',1,0,NULL,NULL,'V04',NULL),(756,NULL,'Verwerkingslocatie FloraH',1,0,NULL,NULL,'V05',NULL),(757,NULL,'FloraHolland Financial',1,0,NULL,NULL,'V06',NULL),(758,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V07',NULL),(759,NULL,'Benefiet veiling',1,0,NULL,NULL,'V08',NULL),(760,NULL,'Kloksoort',1,0,NULL,NULL,'V09',NULL),(761,NULL,'Minimumprijs aanvoerder',1,0,NULL,NULL,'V10',NULL),(762,NULL,'Rest aantallen',1,0,NULL,NULL,'V11',NULL),(763,NULL,'Veilsoort',1,0,NULL,NULL,'V12',NULL),(764,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V13',NULL),(765,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V14',NULL),(766,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V15',NULL),(767,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V16',NULL),(768,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V17',NULL),(769,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V18',NULL),(770,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V19',NULL),(771,NULL,'Gereserveerd',1,0,NULL,NULL,'V20',NULL),(772,NULL,'Veilgroep Aalsmeer',1,0,NULL,NULL,'V21',NULL),(773,NULL,'Promotie kenmerk FloraHol',1,0,NULL,NULL,'V22',NULL),(774,NULL,'Verrekening snijbloemenvo',1,0,NULL,NULL,'V23',NULL),(775,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V24',NULL),(776,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V25',NULL),(777,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V26',NULL),(778,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V27',NULL),(779,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V28',NULL),(780,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V29',NULL),(781,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V30',NULL),(782,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V31',NULL),(783,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V32',NULL),(784,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V33',NULL),(785,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V34',NULL),(786,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V35',NULL),(787,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V36',NULL),(788,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V37',NULL),(789,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V38',NULL),(790,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V39',NULL),(791,NULL,'Gereserveerd',1,0,NULL,NULL,'V40',NULL),(792,NULL,'Tussenopslag klok Plantio',1,0,NULL,NULL,'V41',NULL),(793,NULL,'Soort ladingsdrager Plant',1,0,NULL,NULL,'V42',NULL),(794,NULL,'Logistiek middel Plantion',1,0,NULL,NULL,'V43',NULL),(795,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V44',NULL),(796,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V45',NULL),(797,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V46',NULL),(798,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V47',NULL),(799,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V48',NULL),(800,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V49',NULL),(801,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V50',NULL),(802,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V51',NULL),(803,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V52',NULL),(804,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V53',NULL),(805,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V54',NULL),(806,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V55',NULL),(807,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V56',NULL),(808,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V57',NULL),(809,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V58',NULL),(810,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V59',NULL),(811,NULL,'Gereserveerd',1,0,NULL,NULL,'V60',NULL),(812,NULL,'Veilgroep Plantion Ede',1,0,NULL,NULL,'V61',NULL),(813,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V62',NULL),(814,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V63',NULL),(815,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V64',NULL),(816,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V65',NULL),(817,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V66',NULL),(818,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V67',NULL),(819,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V68',NULL),(820,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V69',NULL),(821,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V70',NULL),(822,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V71',NULL),(823,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V72',NULL),(824,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V73',NULL),(825,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V74',NULL),(826,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V75',NULL),(827,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V76',NULL),(828,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V77',NULL),(829,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V78',NULL),(830,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V79',NULL),(831,NULL,'Toegevoegde waardes VRM',1,0,NULL,NULL,'V80',NULL),(832,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V81',NULL),(833,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V82',NULL),(834,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V83',NULL),(835,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V84',NULL),(836,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V85',NULL),(837,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V86',NULL),(838,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V87',NULL),(839,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V88',NULL),(840,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V89',NULL),(841,NULL,'Veiling',1,0,NULL,NULL,'V99',NULL),(842,NULL,'kopersaantallen',1,0,NULL,NULL,'Z01',NULL),(843,NULL,'Caducidad',1,0,NULL,NULL,NULL,NULL),(844,NULL,'Lote',1,0,NULL,NULL,NULL,NULL),(845,NULL,'Uds palet',1,0,NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `tag` ENABLE KEYS */; UNLOCK TABLES; @@ -287,7 +310,7 @@ UNLOCK TABLES; LOCK TABLES `state` WRITE; /*!40000 ALTER TABLE `state` DISABLE KEYS */; -INSERT INTO `state` VALUES (1,'Arreglar',2,0,'FIXING',NULL,1,0,0,0,0,0,0,4),(2,'Libre',2,0,'FREE',NULL,2,1,0,0,0,1,0,4),(3,'OK',3,0,'OK',3,28,1,0,0,0,1,1,3),(4,'Impreso',4,1,'PRINTED',2,29,1,0,1,0,0,0,2),(5,'Preparación',5,1,'ON_PREPARATION',7,5,0,0,0,2,0,0,2),(6,'En Revisión',7,1,'ON_CHECKING',NULL,6,0,1,0,3,0,0,1),(7,'Sin Acabar',1,0,'NOT_READY',NULL,7,0,0,0,0,1,0,4),(8,'Revisado',8,1,'CHECKED',NULL,8,0,1,0,3,0,0,1),(9,'Encajando',9,2,'PACKING',NULL,9,0,1,0,0,0,0,0),(10,'Encajado',10,2,'PACKED',NULL,10,0,1,0,0,0,0,0),(11,'Facturado',0,0,'INVOICED',NULL,11,0,1,0,0,0,0,0),(12,'Bloqueado',0,0,'BLOCKED',NULL,12,0,0,0,0,0,0,4),(13,'En Reparto',11,3,'ON_DELIVERY',NULL,13,0,1,0,0,0,0,0),(14,'Preparado',6,1,'PREPARED',NULL,14,0,1,0,2,0,0,1),(15,'Pte Recogida',12,3,'WAITING_FOR_PICKUP',NULL,15,0,1,0,0,0,0,0),(16,'Entregado',13,3,'DELIVERED',NULL,16,0,1,0,0,0,0,0),(20,'Asignado',4,1,'PICKER_DESIGNED',NULL,20,1,0,0,0,0,0,2),(21,'Retornado',4,1,'PRINTED_BACK',6,21,0,0,0,0,0,0,2),(22,'¿Fecha?',2,0,'WRONG_DATE',NULL,22,0,0,0,0,0,0,4),(23,'URGENTE',2,0,'LAST_CALL',NULL,23,1,0,0,0,0,0,4),(24,'Encadenado',4,0,'CHAINED',4,24,0,0,0,0,0,0,3),(25,'Embarcando',3,0,'BOARDING',5,25,1,0,0,0,1,0,3),(26,'Prep Previa',5,1,'PREVIOUS_PREPARATION',1,26,0,0,0,1,0,0,2),(27,'Prep Asistida',5,1,'ASSISTED_PREPARATION',7,27,0,0,0,0,0,0,2),(28,'Previa OK',3,1,'OK PREVIOUS',3,28,1,0,0,1,1,1,3),(29,'Previa Impreso',4,1,'PRINTED PREVIOUS',2,29,1,0,1,1,0,0,3),(30,'Embarcado',4,0,'BOARD',5,30,0,0,0,2,0,0,3),(31,'Polizon Impreso',4,1,'PRINTED STOWAWAY',2,29,1,0,1,0,0,0,3),(32,'Polizon OK',3,1,'OK STOWAWAY',3,31,1,0,0,1,1,1,3),(33,'Auto_Impreso',4,1,'PRINTED_AUTO',2,29,1,0,1,0,0,0,2); +INSERT INTO `state` VALUES (1,'Arreglar',2,0,'FIXING',NULL,1,0,0,0,0,0,0,4),(2,'Libre',2,0,'FREE',NULL,2,1,0,0,0,1,0,4),(3,'OK',3,0,'OK',3,28,1,0,0,0,1,1,3),(4,'Impreso',4,0,'PRINTED',2,29,1,0,1,0,0,0,2),(5,'Preparación',5,1,'ON_PREPARATION',7,5,0,0,0,2,0,0,2),(6,'En Revisión',7,1,'ON_CHECKING',NULL,6,0,1,0,3,0,0,1),(7,'Sin Acabar',1,0,'NOT_READY',NULL,7,0,0,0,0,1,0,4),(8,'Revisado',8,1,'CHECKED',NULL,8,0,1,0,3,0,0,1),(9,'Encajando',9,2,'PACKING',NULL,9,0,1,0,0,0,0,0),(10,'Encajado',10,2,'PACKED',NULL,10,0,1,0,0,0,0,0),(11,'Facturado',0,3,'INVOICED',NULL,11,0,1,0,0,0,0,0),(12,'Bloqueado',0,0,'BLOCKED',NULL,12,0,0,0,0,0,0,4),(13,'En Reparto',11,3,'ON_DELIVERY',NULL,13,0,1,0,0,0,0,0),(14,'Preparado',6,1,'PREPARED',NULL,14,0,1,0,2,0,0,1),(15,'Pte Recogida',12,3,'WAITING_FOR_PICKUP',NULL,15,0,1,0,0,0,0,0),(16,'Entregado',13,3,'DELIVERED',NULL,16,0,1,0,0,0,0,0),(20,'Asignado',4,0,'PICKER_DESIGNED',NULL,20,1,0,0,0,0,0,2),(21,'Retornado',4,1,'PRINTED_BACK',6,21,0,0,0,0,0,0,2),(22,'¿Fecha?',2,0,'WRONG_DATE',NULL,22,0,0,0,0,0,0,4),(23,'URGENTE',2,0,'LAST_CALL',NULL,23,1,0,0,0,0,0,4),(24,'Encadenado',4,0,'CHAINED',4,24,0,0,0,0,0,0,3),(25,'Embarcando',3,0,'BOARDING',5,25,1,0,0,0,1,0,3),(26,'Prep Previa',5,1,'PREVIOUS_PREPARATION',1,26,0,0,0,1,0,0,2),(27,'Prep Asistida',5,1,'ASSISTED_PREPARATION',7,27,0,0,0,0,0,0,2),(28,'Previa OK',3,1,'OK PREVIOUS',3,28,1,0,0,1,1,1,3),(29,'Previa Impreso',4,1,'PRINTED PREVIOUS',2,29,1,0,1,1,0,0,3),(30,'Embarcado',4,1,'BOARD',5,30,0,0,0,2,0,0,3),(31,'Polizon Impreso',4,1,'PRINTED STOWAWAY',2,29,1,0,1,0,0,0,3),(32,'Polizon OK',3,1,'OK STOWAWAY',3,31,1,0,0,1,1,1,3),(33,'Auto_Impreso',4,0,'PRINTED_AUTO',2,29,1,0,1,0,0,0,2); /*!40000 ALTER TABLE `state` ENABLE KEYS */; UNLOCK TABLES; @@ -307,7 +330,7 @@ UNLOCK TABLES; LOCK TABLES `department` WRITE; /*!40000 ALTER TABLE `department` DISABLE KEYS */; -INSERT INTO `department` VALUES (1,'VERDNATURA',1,2,763,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(22,'COMPRAS',3,4,NULL,72,596,2,5,0,0,0,0,NULL,'/',NULL,1),(23,'CAMARA',14,19,NULL,72,604,2,6,1,0,1,2,37,'/37/',NULL,0),(31,'INFORMATICA',5,6,NULL,72,127,3,9,0,0,0,0,NULL,'/','informatica',1),(34,'CONTABILIDAD',7,8,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(35,'FINANZAS',9,10,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(36,'LABORAL',11,12,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(37,'PRODUCCION',13,52,NULL,72,230,3,11,1,0,0,17,NULL,'/',NULL,0),(38,'SACADO',20,21,NULL,72,230,4,14,1,0,1,0,37,'/37/',NULL,0),(39,'ENCAJADO',22,23,NULL,72,230,4,12,1,0,1,0,37,'/37/',NULL,0),(41,'ADMINISTRACION',53,54,NULL,72,599,3,8,0,0,0,0,NULL,'/',NULL,1),(43,'VENTAS',55,80,NULL,0,NULL,NULL,NULL,0,0,0,12,NULL,'/',NULL,1),(44,'GERENCIA',81,82,NULL,72,300,2,7,0,0,0,0,NULL,'/',NULL,0),(45,'LOGISTICA',83,84,NULL,72,596,3,19,0,0,0,0,NULL,'/',NULL,1),(46,'REPARTO',85,88,NULL,72,659,3,10,0,0,0,1,NULL,'/',NULL,0),(48,'ALMACENAJE',89,90,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(49,'PROPIEDAD',91,92,NULL,72,1008,1,1,0,0,0,0,NULL,'/',NULL,0),(52,'CARGA AEREA',93,94,NULL,72,163,4,28,0,0,0,0,NULL,'/',NULL,0),(53,'MARKETING Y COMUNICACIÓN',95,96,NULL,72,1238,0,0,0,0,0,0,NULL,'/',NULL,1),(54,'ORNAMENTALES',97,98,NULL,72,433,3,21,0,0,0,0,NULL,'/',NULL,0),(55,'TALLER NATURAL',99,100,NULL,72,695,2,23,0,0,0,0,NULL,'/',NULL,0),(56,'TALLER ARTIFICIAL',101,102,NULL,72,1780,2,24,0,0,0,0,NULL,'/',NULL,0),(58,'CAMPOS',103,104,NULL,72,225,2,2,0,0,0,0,NULL,'/',NULL,0),(59,'MANTENIMIENTO',105,106,NULL,72,1907,4,16,0,0,0,0,NULL,'/',NULL,0),(60,'RECLAMACIONES',107,108,NULL,72,563,3,20,0,0,0,0,NULL,'/',NULL,1),(61,'VNH',109,110,NULL,73,1297,3,17,0,0,0,0,NULL,'/',NULL,0),(63,'VENTAS FRANCIA',56,57,NULL,72,277,2,27,0,0,1,0,43,'/43/',NULL,0),(66,'VERDNAMADRID',111,112,NULL,72,163,3,18,0,0,0,0,NULL,'/',NULL,0),(68,'COMPLEMENTOS',24,25,NULL,72,617,3,26,1,0,1,0,37,'/37/',NULL,0),(69,'VERDNABARNA',113,114,NULL,74,432,3,22,0,0,0,0,NULL,'/',NULL,0),(77,'PALETIZADO',86,87,NULL,72,230,4,15,1,0,1,0,46,'/46/',NULL,0),(80,'EQUIPO J VALLES',58,59,NULL,72,693,3,4,0,0,1,0,43,'/43/','jvp_equipo',1),(86,'LIMPIEZA',115,116,NULL,72,599,0,0,0,0,0,0,NULL,'/',NULL,0),(89,'COORDINACION',117,118,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(90,'TRAILER',119,120,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(91,'ARTIFICIAL',26,27,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(92,'EQUIPO SILVERIO',60,61,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','sdc_equipo',1),(93,'CONFECCION',121,122,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(94,'EQUIPO J BROCAL',62,63,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jes_equipo',1),(95,'EQUIPO C ZAMBRANO',64,65,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','czg_equipo',1),(96,'EQUIPO C LOPEZ',66,67,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','cla_equipo',1),(97,'EQUIPO D SARRION',68,69,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/',NULL,1),(98,'EQUIPO RODRIGO',70,71,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','rhr_equipo',1),(99,'EQUIPO MANOLI',72,73,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/',NULL,1),(101,'EQUIPO J IBAÑEZ',74,75,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jmi_equipo',1),(102,'EQ ROJO FV RUBEN C',28,29,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(103,'EQ AZUL FV A FOLQUES',30,31,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(104,'EQ AMARILLO FV NORMAN G',32,33,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(105,'EQ MORADO FV MATOU',34,35,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(106,'EQ VERDE PCA KEVIN GIMENEZ',36,37,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(107,'EQ NARANJA PCA RUBEN ZANON',38,39,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(110,'EQ ROSA PCA J BONDIA',40,41,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(111,'EQ REPONEDOR CAJAS',42,43,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(112,'CAMARA EQ EDGAR LLEO',15,16,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(113,'CAMARA EQ MARC ROCA',17,18,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(114,'EQ MARRON PCA JL NUEVO',44,45,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(115,'EQUIPO CLAUDI',76,77,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','csr_equipo',1),(120,'PCA PRODUCCION',46,47,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(121,'FV PRODUCCION',48,49,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(122,'PCA ALMACEN',50,51,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(123,'EQUIPO ELENA BASCUÑANA',78,79,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','ebt_equipo',0); +INSERT INTO `department` VALUES (1,'VERDNATURA',1,2,763,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(22,'COMPRAS',3,4,NULL,72,596,2,5,0,0,0,0,NULL,'/',NULL,1),(23,'CAMARA',14,19,NULL,72,604,2,6,1,0,1,2,37,'/37/',NULL,0),(31,'INFORMATICA',5,6,NULL,72,127,3,9,0,0,0,0,NULL,'/','informatica',1),(34,'CONTABILIDAD',7,8,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(35,'FINANZAS',9,10,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(36,'LABORAL',11,12,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(37,'PRODUCCION',13,52,NULL,72,230,3,11,1,0,0,17,NULL,'/',NULL,0),(38,'SACADO',20,21,NULL,72,230,4,14,1,0,1,0,37,'/37/',NULL,0),(39,'ENCAJADO',22,23,NULL,72,230,4,12,1,0,1,0,37,'/37/',NULL,0),(41,'ADMINISTRACION',53,54,NULL,72,599,3,8,0,0,0,0,NULL,'/',NULL,1),(43,'VENTAS',55,76,NULL,0,NULL,NULL,NULL,0,0,0,10,NULL,'/',NULL,1),(44,'GERENCIA',77,78,NULL,72,300,2,7,0,0,0,0,NULL,'/',NULL,0),(45,'LOGISTICA',79,80,NULL,72,596,3,19,0,0,0,0,NULL,'/',NULL,1),(46,'REPARTO',81,84,NULL,72,659,3,10,0,0,0,1,NULL,'/',NULL,0),(48,'ALMACENAJE',85,86,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(49,'PROPIEDAD',87,88,NULL,72,1008,1,1,0,0,0,0,NULL,'/',NULL,0),(52,'CARGA AEREA',89,90,NULL,72,163,4,28,0,0,0,0,NULL,'/',NULL,0),(53,'MARKETING Y COMUNICACIÓN',91,92,NULL,72,1238,0,0,0,0,0,0,NULL,'/',NULL,1),(54,'ORNAMENTALES',93,94,NULL,72,433,3,21,0,0,0,0,NULL,'/',NULL,0),(55,'TALLER NATURAL',95,96,NULL,72,695,2,23,0,0,0,0,NULL,'/',NULL,0),(56,'TALLER ARTIFICIAL',97,98,NULL,72,1780,2,24,0,0,0,0,NULL,'/',NULL,0),(58,'CAMPOS',99,100,NULL,72,225,2,2,0,0,0,0,NULL,'/',NULL,0),(59,'MANTENIMIENTO',101,102,NULL,72,1907,4,16,0,0,0,0,NULL,'/',NULL,0),(60,'RECLAMACIONES',103,104,NULL,72,563,3,20,0,0,0,0,NULL,'/',NULL,1),(61,'VNH',105,106,NULL,73,1297,3,17,0,0,0,0,NULL,'/',NULL,0),(63,'VENTAS FRANCIA',56,57,NULL,72,277,2,27,0,0,1,0,43,'/43/',NULL,0),(66,'VERDNAMADRID',107,108,NULL,72,163,3,18,0,0,0,0,NULL,'/',NULL,0),(68,'COMPLEMENTOS',24,25,NULL,72,617,3,26,1,0,1,0,37,'/37/',NULL,0),(69,'VERDNABARNA',109,110,NULL,74,432,3,22,0,0,0,0,NULL,'/',NULL,0),(77,'PALETIZADO',82,83,NULL,72,230,4,15,1,0,1,0,46,'/46/',NULL,0),(80,'EQUIPO J VALLES',58,59,NULL,72,693,3,4,0,0,1,0,43,'/43/','jvp_equipo',1),(86,'LIMPIEZA',111,112,NULL,72,599,0,0,0,0,0,0,NULL,'/',NULL,0),(89,'COORDINACION',113,114,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(90,'TRAILER',115,116,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(91,'ARTIFICIAL',26,27,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(92,'EQUIPO SILVERIO',60,61,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','sdc_equipo',1),(93,'CONFECCION',117,118,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(94,'EQUIPO J BROCAL',62,63,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jes_equipo',1),(95,'EQUIPO C ZAMBRANO',64,65,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','czg_equipo',1),(96,'EQUIPO C LOPEZ',66,67,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','cla_equipo',1),(98,'EQUIPO RODRIGO',68,69,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','rhr_equipo',1),(101,'EQUIPO J IBAÑEZ',70,71,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jmi_equipo',1),(102,'EQ ROJO FV RUBEN C',28,29,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(103,'EQ AZUL FV A FOLQUES',30,31,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(104,'EQ AMARILLO FV NORMAN G',32,33,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(105,'EQ MORADO FV MATOU',34,35,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(106,'EQ VERDE PCA KEVIN GIMENEZ',36,37,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(107,'EQ NARANJA PCA RUBEN ZANON',38,39,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(110,'EQ ROSA PCA J BONDIA',40,41,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(111,'EQ REPONEDOR CAJAS',42,43,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(112,'CAMARA EQ EDGAR LLEO',15,16,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(113,'CAMARA EQ MARC ROCA',17,18,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(114,'EQ MARRON PCA JL NUEVO',44,45,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(115,'EQUIPO CLAUDI',72,73,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','csr_equipo',1),(120,'PCA PRODUCCION',46,47,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(121,'FV PRODUCCION',48,49,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(122,'PCA ALMACEN',50,51,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(123,'EQUIPO ELENA BASCUÑANA',74,75,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','ebt_equipo',0); /*!40000 ALTER TABLE `department` ENABLE KEYS */; UNLOCK TABLES; @@ -334,27 +357,25 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:35 +-- Dump completed on 2020-09-12 1:01:02 USE `cache`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: cache +-- Host: test-db.verdnatura.es Database: cache -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -372,27 +393,25 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:35 +-- Dump completed on 2020-09-12 1:01:02 USE `hedera`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: hedera +-- Host: test-db.verdnatura.es Database: hedera -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -430,27 +449,25 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:36 +-- Dump completed on 2020-09-12 1:01:03 USE `postgresql`; --- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) +-- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: db.verdnatura.es Database: postgresql +-- Host: test-db.verdnatura.es Database: postgresql -- ------------------------------------------------------ --- Server version 5.6.25-4-log +-- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -518,10 +535,9 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-09 11:46:38 +-- Dump completed on 2020-09-12 1:01:04 diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index d339506db..416c5c6c4 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -36,6 +36,9 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active` FROM `account`.`role` WHERE id <> 20 ORDER BY id; +INSERT INTO `account`.`account`(`id`) + SELECT id FROM `account`.`user`; + INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `userFk`, `bossFk`) SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id, 9 FROM `vn`.`user`; @@ -65,20 +68,6 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`, (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'), (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'); - -INSERT INTO `account`.`userPassword` (`id`, `length`, `nAlpha`, `nUpper`, `nDigits`, `nPunct`) - VALUES - (1, 8, 1, 1, 1, 1); - -INSERT INTO `account`.`account`(`id`) - VALUES - (101), - (102); - -INSERT INTO `account`.`mailConfig` (`id`, `domain`) - VALUES - (1, 'verdnatura.es'); - INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) VALUES (1, 'general', 'General mailing list', FALSE), diff --git a/db/export-data.sh b/db/export-data.sh index 9f3997cf7..0aa9fb319 100755 --- a/db/export-data.sh +++ b/db/export-data.sh @@ -22,6 +22,9 @@ TABLES=( role roleInherit roleRole + userPassword + accountConfig + mailConfig ) dump_tables ${TABLES[@]} diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 285617a77..ea5a09952 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -141,5 +141,7 @@ "Role already assigned": "Role already assigned", "Invalid role name": "Invalid role name", "Role name must be written in camelCase": "Role name must be written in camelCase", - "can't be set": "can't be set" + "can't be set": "can't be set", + "Email already exists": "Email already exists", + "User already exists": "User already exists" } \ No newline at end of file diff --git a/modules/account/back/methods/role-inherit/sync.js b/modules/account/back/methods/role-inherit/sync.js new file mode 100644 index 000000000..1b6a3cd83 --- /dev/null +++ b/modules/account/back/methods/role-inherit/sync.js @@ -0,0 +1,114 @@ +const ldap = require('../../util/ldapjs-extra'); + +module.exports = Self => { + Self.remoteMethod('sync', { + description: 'Synchronizes the user with the other user databases', + http: { + path: `/sync`, + verb: 'PATCH' + } + }); + + Self.sync = async function() { + let $ = Self.app.models; + + let ldapConfig = await $.LdapConfig.findOne({ + fields: ['host', 'rdn', 'password', 'groupDn'] + }); + let accountConfig = await $.AccountConfig.findOne({ + fields: ['idBase'] + }); + + if (!ldapConfig) return; + + // Connect + + let client = ldap.createClient({ + url: `ldap://${ldapConfig.host}:389` + }); + + let ldapPassword = Buffer + .from(ldapConfig.password, 'base64') + .toString('ascii'); + await client.bind(ldapConfig.rdn, ldapPassword); + + let err; + try { + // Delete roles + + let opts = { + scope: 'sub', + attributes: ['dn'], + filter: 'objectClass=posixGroup' + }; + res = await client.search(ldapConfig.groupDn, opts); + + let reqs = []; + await new Promise((resolve, reject) => { + res.on('error', err => { + if (err.name === 'NoSuchObjectError') + err = new Error(`Object '${ldapConfig.groupDn}' does not exist`); + reject(err); + }); + res.on('searchEntry', e => { + reqs.push(client.del(e.object.dn)); + }); + res.on('end', resolve); + }); + await Promise.all(reqs); + + // Recreate roles + + let roles = await $.Role.find({ + fields: ['id', 'name'] + }); + let accounts = await $.UserAccount.find({ + fields: ['id'], + include: { + relation: 'user', + scope: { + fields: ['name'], + include: { + relation: 'roles', + scope: { + fields: ['inheritsFrom'] + } + } + } + } + }); + + let map = new Map(); + for (let account of accounts) { + let user = account.user(); + for (let inherit of user.roles()) { + let roleId = inherit.inheritsFrom; + if (!map.has(roleId)) map.set(roleId, []); + map.get(roleId).push(user.name); + } + } + + reqs = []; + for (let role of roles) { + let newEntry = { + objectClass: ['top', 'posixGroup'], + cn: role.name, + gidNumber: accountConfig.idBase + role.id + }; + + let memberUid = map.get(role.id); + if (memberUid) newEntry.memberUid = memberUid; + + let dn = `cn=${role.name},${ldapConfig.groupDn}`; + reqs.push(client.add(dn, newEntry)); + } + await Promise.all(reqs); + } catch (e) { + err = e; + } + + // FIXME: Cannot disconnect, hangs on undind() call + // await client.unbind(); + if (err) throw err; + }; +}; diff --git a/modules/account/back/methods/user-account/sync-by-id.js b/modules/account/back/methods/user-account/sync-by-id.js new file mode 100644 index 000000000..3c292dd4c --- /dev/null +++ b/modules/account/back/methods/user-account/sync-by-id.js @@ -0,0 +1,27 @@ + +module.exports = Self => { + Self.remoteMethod('syncById', { + description: 'Synchronizes the user with the other user databases', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The user id', + required: true + }, { + arg: 'password', + type: 'string', + description: 'The password' + } + ], + http: { + path: `/:id/syncById`, + verb: 'PATCH' + } + }); + + Self.syncById = async function(id, password) { + let user = await Self.app.models.Account.findById(id, {fields: ['name']}); + await Self.sync(user.name, password); + }; +}; diff --git a/modules/account/back/methods/user-account/sync.js b/modules/account/back/methods/user-account/sync.js new file mode 100644 index 000000000..681f3674d --- /dev/null +++ b/modules/account/back/methods/user-account/sync.js @@ -0,0 +1,268 @@ +const ldap = require('../../util/ldapjs-extra'); +const nthash = require('smbhash').nthash; +const ssh = require('node-ssh'); +const crypto = require('crypto'); + +module.exports = Self => { + Self.remoteMethod('sync', { + description: 'Synchronizes the user with the other user databases', + accepts: [ + { + arg: 'userName', + type: 'string', + description: 'The user name', + required: true + }, { + arg: 'password', + type: 'string', + description: 'The password' + } + ], + http: { + path: `/sync`, + verb: 'PATCH' + } + }); + + Self.sync = async function(userName, password) { + let $ = Self.app.models; + + let user = await $.Account.findOne({ + fields: ['id', 'sync'], + where: {name: userName} + }); + + if (user && user.sync) return; + + let accountConfig; + let mailConfig; + let extraParams; + let hasAccount = false; + + if (user) { + accountConfig = await $.AccountConfig.findOne({ + fields: ['homedir', 'shell', 'idBase'] + }); + mailConfig = await $.MailConfig.findOne({ + fields: ['domain'] + }); + + user = await $.Account.findById(user.id, { + fields: [ + 'id', + 'nickname', + 'email', + 'lang', + 'roleFk', + 'sync', + 'active', + 'created', + 'updated' + ], + where: {name: userName}, + include: { + relation: 'roles', + scope: { + include: { + relation: 'inherits', + scope: { + fields: ['name'] + } + } + } + } + }); + + extraParams = { + corporateMail: `${userName}@${mailConfig.domain}`, + uidNumber: accountConfig.idBase + user.id + }; + + hasAccount = user.active + && await $.UserAccount.exists(user.id); + } + + if (user) { + let bcryptPassword = $.User.hashPassword(password); + await $.Account.upsertWithWhere({id: user.id}, {bcryptPassword}); + + await $.user.destroyById(user.id); + if (hasAccount) { + await $.user.upsert({ + id: user.id, + username: userName, + password: bcryptPassword, + email: user.email, + created: user.created, + updated: user.updated + }); + } + } + + // SIP + + if (hasAccount) { + await Self.rawSql('CALL pbx.sip_setPassword(?, ?)', + [user.id, password] + ); + } + + // LDAP + + let ldapConfig = await $.LdapConfig.findOne({ + fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn'] + }); + + if (ldapConfig) { + let ldapClient = ldap.createClient({ + url: `ldap://${ldapConfig.host}:389` + }); + + let ldapPassword = Buffer + .from(ldapConfig.password, 'base64') + .toString('ascii'); + await ldapClient.bind(ldapConfig.rdn, ldapPassword); + + let err; + try { + // Deletes user + + try { + let dn = `uid=${userName},${ldapConfig.baseDn}`; + await ldapClient.del(dn); + } catch (e) { + if (e.name !== 'NoSuchObjectError') throw e; + } + + // Removes user from groups + + let opts = { + scope: 'sub', + attributes: ['dn'], + filter: `&(memberUid=${userName})(objectClass=posixGroup)` + }; + res = await ldapClient.search(ldapConfig.groupDn, opts); + + let oldGroups = []; + await new Promise((resolve, reject) => { + res.on('error', reject); + res.on('searchEntry', e => oldGroups.push(e.object)); + res.on('end', resolve); + }); + + let reqs = []; + for (oldGroup of oldGroups) { + let change = new ldap.Change({ + operation: 'delete', + modification: {memberUid: userName} + }); + reqs.push(ldapClient.modify(oldGroup.dn, change)); + } + await Promise.all(reqs); + + if (hasAccount) { + // Recreates user + + let nameArgs = user.nickname.split(' '); + let sshaPassword = crypto + .createHash('sha1') + .update(password) + .digest('base64'); + + let dn = `uid=${userName},${ldapConfig.baseDn}`; + let newEntry = { + uid: userName, + objectClass: [ + 'inetOrgPerson', + 'posixAccount', + 'sambaSamAccount' + ], + cn: user.nickname || userName, + displayName: user.nickname, + givenName: nameArgs[0], + sn: nameArgs[1] || 'Empty', + mail: extraParams.corporateMail, + userPassword: `{SSHA}${sshaPassword}`, + preferredLanguage: user.lang, + homeDirectory: `${accountConfig.homedir}/${userName}`, + loginShell: accountConfig.shell, + uidNumber: extraParams.uidNumber, + gidNumber: accountConfig.idBase + user.roleFk, + sambaSID: '-', + sambaNTPassword: nthash(password) + }; + await ldapClient.add(dn, newEntry); + + // Adds user to groups + + let reqs = []; + for (let role of user.roles()) { + let change = new ldap.Change({ + operation: 'add', + modification: {memberUid: userName} + }); + let roleName = role.inherits().name; + let dn = `cn=${roleName},${ldapConfig.groupDn}`; + reqs.push(ldapClient.modify(dn, change)); + } + await Promise.all(reqs); + } + } catch (e) { + err = e; + } + + // FIXME: Cannot disconnect, hangs on undind() call + // await ldapClient.unbind(); + if (err) throw err; + } + + // Samba + + let sambaConfig = await $.SambaConfig.findOne({ + fields: ['host', 'sshUser', 'sshPass'] + }); + + if (sambaConfig) { + let sshPassword = Buffer + .from(sambaConfig.sshPass, 'base64') + .toString('ascii'); + + let sshClient = new ssh.NodeSSH(); + await sshClient.connect({ + host: sambaConfig.host, + username: sambaConfig.sshUser, + password: sshPassword + }); + + let commands; + + if (hasAccount) { + commands = [ + `samba-tool user create "${userName}" ` + + `--uid-number=${extraParams.uidNumber} ` + + `--mail-address="${extraParams.corporateMail}" ` + + `--random-password`, + `samba-tool user setexpiry "${userName}" ` + + `--noexpiry`, + `samba-tool user setpassword "${userName}" ` + + `--newpassword="${password}"`, + `mkhomedir_helper "${userName}" 0027` + ]; + } else { + commands = [ + `samba-tool user delete "${userName}"` + ]; + } + + for (let command of commands) + await sshClient.execCommand(command); + + await sshClient.dispose(); + } + + // Mark as synchronized + + // if (user) + // await $.Account.upsertWithWhere({id: user.id}, {sync: true}); + }; +}; diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json index 48d7c427d..9700ed0a1 100644 --- a/modules/account/back/model-config.json +++ b/modules/account/back/model-config.json @@ -1,10 +1,19 @@ { + "AccountConfig": { + "dataSource": "vn" + }, + "LdapConfig": { + "dataSource": "vn" + }, "MailAlias": { "dataSource": "vn" }, "MailAliasAccount": { "dataSource": "vn" }, + "MailConfig": { + "dataSource": "vn" + }, "MailForward": { "dataSource": "vn" }, @@ -14,11 +23,13 @@ "RoleRole": { "dataSource": "vn" }, + "SambaConfig": { + "dataSource": "vn" + }, "UserAccount": { "dataSource": "vn" }, "UserPassword": { "dataSource": "vn" } - } \ No newline at end of file diff --git a/modules/account/back/models/account-config.json b/modules/account/back/models/account-config.json new file mode 100644 index 000000000..a2a405610 --- /dev/null +++ b/modules/account/back/models/account-config.json @@ -0,0 +1,43 @@ +{ + "name": "AccountConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.accountConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "homedir": { + "type": "string", + "required": true + }, + "shell": { + "type": "string", + "required": true + }, + "idBase": { + "type": "number", + "required": true + }, + "min": { + "type": "number", + "required": true + }, + "max": { + "type": "number", + "required": true + }, + "warn": { + "type": "number", + "required": true + }, + "inact": { + "type": "number", + "required": true + } + } +} diff --git a/modules/account/back/models/ldap-config.json b/modules/account/back/models/ldap-config.json new file mode 100644 index 000000000..e3061d651 --- /dev/null +++ b/modules/account/back/models/ldap-config.json @@ -0,0 +1,36 @@ +{ + "name": "LdapConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.ldapConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "host": { + "type": "string", + "required": true + }, + "rdn": { + "type": "string", + "required": true + }, + "password": { + "type": "string", + "required": true + }, + "baseDn": { + "type": "string" + }, + "filter": { + "type": "string" + }, + "groupDn": { + "type": "string" + } + } +} diff --git a/modules/account/back/models/mail-config.json b/modules/account/back/models/mail-config.json new file mode 100644 index 000000000..1b3d31fd8 --- /dev/null +++ b/modules/account/back/models/mail-config.json @@ -0,0 +1,19 @@ +{ + "name": "MailConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.mailConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "domain": { + "type": "string", + "required": true + } + } +} diff --git a/modules/account/back/models/role-role.js b/modules/account/back/models/role-inherit.js similarity index 86% rename from modules/account/back/models/role-role.js rename to modules/account/back/models/role-inherit.js index e2e860187..7d31e62b1 100644 --- a/modules/account/back/models/role-role.js +++ b/modules/account/back/models/role-inherit.js @@ -1,10 +1,12 @@ const app = require('vn-loopback/server/server'); module.exports = Self => { + require('../methods/role-inherit/sync')(Self); + app.on('started', function() { let hooks = ['after save', 'after delete']; for (let hook of hooks) { - app.models.RoleInherit.observe(hook, async() => { + Self.observe(hook, async() => { try { await Self.rawSql(` CREATE EVENT account.role_sync diff --git a/modules/account/back/models/samba-config.json b/modules/account/back/models/samba-config.json new file mode 100644 index 000000000..ffbcce4eb --- /dev/null +++ b/modules/account/back/models/samba-config.json @@ -0,0 +1,25 @@ +{ + "name": "SambaConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.sambaConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "host": { + "type": "string", + "required": true + }, + "sshUser": { + "type": "string" + }, + "sshPass": { + "type": "string" + } + } +} diff --git a/modules/account/back/models/user-account.js b/modules/account/back/models/user-account.js new file mode 100644 index 000000000..6fb8fd103 --- /dev/null +++ b/modules/account/back/models/user-account.js @@ -0,0 +1,5 @@ + +module.exports = Self => { + require('../methods/user-account/sync')(Self); + require('../methods/user-account/sync-by-id')(Self); +}; diff --git a/modules/account/back/util/ldapjs-extra.js b/modules/account/back/util/ldapjs-extra.js new file mode 100644 index 000000000..381eebb6f --- /dev/null +++ b/modules/account/back/util/ldapjs-extra.js @@ -0,0 +1,30 @@ +const ldap = require('ldapjs'); +const promisifyObject = require('./promisify').promisifyObject; + +module.exports = { + createClient, + Change: ldap.Change +}; + +/** + * Creates a promisified version of LDAP client. + * + * @param {Object} opts Client options + * @return {Client} The promisified LDAP client + */ +function createClient(opts) { + let client = ldap.createClient(opts); + promisifyObject(client, [ + 'bind', + 'add', + 'compare', + 'del', + 'exop', + 'modify', + 'modifyDN', + 'search', + 'starttls', + 'unbind' + ]); + return client; +} diff --git a/modules/account/back/util/promisify.js b/modules/account/back/util/promisify.js new file mode 100644 index 000000000..c23cbb36d --- /dev/null +++ b/modules/account/back/util/promisify.js @@ -0,0 +1,47 @@ + +module.exports = { + promisify, + promisifyObject +}; + +/** + * Promisifies a function wich follows the (err, res) => {} pattern as last + * function argument and returns the promisified version. + * + * @param {Function} fn Function to promisify + * @return {Function} The promisified function + */ +function promisify(fn) { + return function(...args) { + let thisArg = this; + let orgCb = args[args.length - 1]; + if (typeof orgCb !== 'function') orgCb = null; + + return new Promise(function(resolve, reject) { + function cb(err, res) { + if (orgCb) orgCb(err, res); + err ? reject(err) : resolve(res); + } + + if (orgCb) + args[args.length - 1] = cb; + else + args.push(cb); + + fn.apply(thisArg, args); + }); + }; +} + +/** + * Promisifies object methods. + * + * @param {Object} obj Object to promisify + * @param {Array} methods Array of method names to promisify + */ +function promisifyObject(obj, methods) { + for (let method of methods) { + let orgMethod = obj[method]; + obj[method] = promisify(orgMethod); + } +} diff --git a/modules/account/front/basic-data/index.html b/modules/account/front/basic-data/index.html index ca87d14b4..d21dda45a 100644 --- a/modules/account/front/basic-data/index.html +++ b/modules/account/front/basic-data/index.html @@ -12,7 +12,7 @@ diff --git a/modules/account/front/connections/index.html b/modules/account/front/connections/index.html index 419a9744a..d634b7a9f 100644 --- a/modules/account/front/connections/index.html +++ b/modules/account/front/connections/index.html @@ -23,7 +23,7 @@ + icon="exit_to_app"> diff --git a/modules/account/front/role/subroles/index.html b/modules/account/front/role/subroles/index.html index 56c6770c3..bc554b9f9 100644 --- a/modules/account/front/role/subroles/index.html +++ b/modules/account/front/role/subroles/index.html @@ -19,7 +19,7 @@ + vn-click-stop="removeConfirm.show(row)"> diff --git a/package-lock.json b/package-lock.json index f576f5d24..601f6681b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6887,6 +6887,11 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "bigdecimal": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/bigdecimal/-/bigdecimal-0.6.1.tgz", + "integrity": "sha1-GFiNS08ia3cxDtBFdIWMA2pUSFs=" + }, "bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", @@ -20459,6 +20464,33 @@ } } }, + "node-ssh": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/node-ssh/-/node-ssh-11.0.0.tgz", + "integrity": "sha512-zC8TuZX82/x/ZsH4GzE5jmUGQAS2ajcDOoD352x6W56A7+4ChuGdGYi+t9NnLKEtRYvbvNH4HO+LvlQVENmxog==", + "requires": { + "make-dir": "^3.1.0", + "sb-promise-queue": "^2.1.0", + "sb-scandir": "^3.1.0", + "shell-escape": "^0.2.0", + "ssh2": "^0.8.9" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "node.extend": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", @@ -22644,6 +22676,19 @@ "xmlchars": "^2.2.0" } }, + "sb-promise-queue": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sb-promise-queue/-/sb-promise-queue-2.1.0.tgz", + "integrity": "sha512-zwq4YuP1FQFkGx2Q7GIkZYZ6PqWpV+bg0nIO1sJhWOyGyhqbj0MsTvK6lCFo5TQwX5pZr6SCQ75e8PCDCuNvkg==" + }, + "sb-scandir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sb-scandir/-/sb-scandir-3.1.0.tgz", + "integrity": "sha512-70BVm2xz9jn94zSQdpvYrEG101/UV9TVGcfWr9T5iob3QhCK4lYXeculfBqPGFv3XTeKgx4dpWyYIDeZUqo4kg==", + "requires": { + "sb-promise-queue": "^2.1.0" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -22883,7 +22928,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -22964,6 +23009,11 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "shell-escape": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/shell-escape/-/shell-escape-0.2.0.tgz", + "integrity": "sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM=" + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -23062,6 +23112,14 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "smbhash": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/smbhash/-/smbhash-0.0.1.tgz", + "integrity": "sha1-Pgtzz8bALUwMGamT6E5S4R/3oJk=", + "requires": { + "bigdecimal": ">= 0.6.0" + } + }, "smtp-connection": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", @@ -23474,6 +23532,24 @@ "options": "0.0.6" } }, + "ssh2": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz", + "integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==", + "requires": { + "ssh2-streams": "~0.4.10" + } + }, + "ssh2-streams": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz", + "integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==", + "requires": { + "asn1": "~0.2.0", + "bcrypt-pbkdf": "^1.0.2", + "streamsearch": "~0.1.2" + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -23628,6 +23704,11 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-length": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", diff --git a/package.json b/package.json index 26175fd60..586b77108 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,14 @@ "loopback-connector-remote": "^3.4.1", "loopback-context": "^3.4.0", "md5": "^2.2.1", + "node-ssh": "^11.0.0", "object-diff": "0.0.4", "object.pick": "^1.3.0", "request": "^2.88.0", "request-promise-native": "^1.0.8", "require-yaml": "0.0.1", "sharp": "^0.25.4", + "smbhash": "0.0.1", "soap": "^0.26.0", "strong-error-handler": "^2.3.2", "uuid": "^3.3.3", -- 2.40.1 From 2b05827be14475adfceb4dc95341d32adf2147d3 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 21 Sep 2020 14:10:26 +0200 Subject: [PATCH 4/7] Merge --- modules/client/front/sample/create/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/client/front/sample/create/index.html b/modules/client/front/sample/create/index.html index 6ec33f8fd..d8bd4712a 100644 --- a/modules/client/front/sample/create/index.html +++ b/modules/client/front/sample/create/index.html @@ -61,7 +61,6 @@ - -- 2.40.1 From 3880e21ce3b1fc0a73a8c41d2eeb0cab4c2966a9 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 21 Sep 2020 16:09:34 +0200 Subject: [PATCH 5/7] Test fixes --- back/models/account.js | 10 +- back/models/account.json | 3 - db/changes/10211-accountModule/00-account.sql | 53 -------- db/changes/10221-accountModule/00-account.sql | 127 ++++++++++++++++++ .../00-myUserChangePassword.sql | 0 .../00-myUserCheckLogin.sql | 0 .../00-myUserGetId.sql | 0 .../00-myUserGetName.sql | 0 .../00-myUserHasRole.sql | 0 .../00-myUserHasRoleId.sql | 0 .../00-myUser_changePassword.sql | 0 .../00-myUser_checkLogin.sql | 0 .../00-myUser_getId.sql | 0 .../00-myUser_getName.sql | 0 .../00-myUser_hasRole.sql | 0 .../00-myUser_hasRoleId.sql | 0 .../00-myUser_login.sql | 0 .../00-myUser_loginWithKey.sql | 0 .../00-myUser_loginWithName.sql | 0 .../00-myUser_logout.sql | 0 .../00-passwordGenerate.sql | 0 .../00-role_checkName.sql | 0 .../00-role_getDescendents.sql | 0 .../00-role_sync.sql | 0 .../00-role_syncPrivileges.sql | 0 .../00-userGetId.sql | 0 .../00-userGetMysqlRole.sql | 0 .../00-userGetName.sql | 0 .../00-userGetNameFromId.sql | 0 .../00-userHasRole.sql | 0 .../00-userHasRoleId.sql | 0 .../00-userLogin.sql | 0 .../00-userLoginWithKey.sql | 0 .../00-userLoginWithName.sql | 0 .../00-userLogout.sql | 0 .../00-userSetPassword.sql | 0 .../00-user_changePassword.sql | 0 .../00-user_checkName.sql | 0 .../00-user_getMysqlRole.sql | 0 .../00-user_getNameFromId.sql | 0 .../00-user_hasRole.sql | 0 .../00-user_hasRoleId.sql | 0 .../00-user_setPassword.sql | 0 front/salix/components/layout/index.js | 2 +- .../salix/components/user-popover/index.html | 2 +- .../account/back/methods/user-account/sync.js | 31 +++-- modules/account/back/model-config.json | 3 + modules/account/back/models/user-sync.json | 15 +++ .../back/methods/client/specs/sendSms.spec.js | 3 +- modules/client/back/methods/sms/send.spec.js | 3 +- .../back/methods/ticket/specs/sendSms.spec.js | 3 +- 51 files changed, 169 insertions(+), 86 deletions(-) delete mode 100644 db/changes/10211-accountModule/00-account.sql create mode 100644 db/changes/10221-accountModule/00-account.sql rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserChangePassword.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserCheckLogin.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserGetId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserGetName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserHasRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUserHasRoleId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_changePassword.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_checkLogin.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_getId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_getName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_hasRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_hasRoleId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_login.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_loginWithKey.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_loginWithName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-myUser_logout.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-passwordGenerate.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-role_checkName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-role_getDescendents.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-role_sync.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-role_syncPrivileges.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userGetId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userGetMysqlRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userGetName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userGetNameFromId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userHasRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userHasRoleId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userLogin.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userLoginWithKey.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userLoginWithName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userLogout.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-userSetPassword.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_changePassword.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_checkName.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_getMysqlRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_getNameFromId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_hasRole.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_hasRoleId.sql (100%) rename db/changes/{10211-accountModule => 10221-accountModule}/00-user_setPassword.sql (100%) create mode 100644 modules/account/back/models/user-sync.json diff --git a/back/models/account.js b/back/models/account.js index b74a14997..1c6ac04f8 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -40,17 +40,9 @@ module.exports = Self => { Self.getCurrentUserData = async function(ctx) { let userId = ctx.req.accessToken.userId; - - let account = await Self.findById(userId, { + return await Self.findById(userId, { fields: ['id', 'name', 'nickname'] }); - - let worker = await Self.app.models.Worker.findOne({ - fields: ['id'], - where: {userFk: userId} - }); - - return Object.assign(account, {workerId: worker.id}); }; /** diff --git a/back/models/account.json b/back/models/account.json index d29e1f38c..483973515 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -45,9 +45,6 @@ }, "updated": { "type": "date" - }, - "sync": { - "type": "boolean" } }, "relations": { diff --git a/db/changes/10211-accountModule/00-account.sql b/db/changes/10211-accountModule/00-account.sql deleted file mode 100644 index 3204f1417..000000000 --- a/db/changes/10211-accountModule/00-account.sql +++ /dev/null @@ -1,53 +0,0 @@ - -ALTER TABLE `account`.`role` - MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL; - -ALTER TABLE `account`.`roleInherit` - ADD UNIQUE( `role`, `inheritsFrom`); - -ALTER TABLE `account`.`roleInherit` - DROP PRIMARY KEY; - -ALTER TABLE `account`.`roleInherit` - ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, - ADD PRIMARY KEY (`id`); - -ALTER TABLE `account`.`mailAlias` - ADD `description` VARCHAR(255) NULL AFTER `alias`; - -ALTER TABLE `account`.`mailAliasAccount` - ADD UNIQUE( `mailAlias`, `account`); - -ALTER TABLE `account`.`mailAliasAccount` - DROP PRIMARY KEY; - -ALTER TABLE `account`.`mailAliasAccount` - ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, - ADD PRIMARY KEY (`id`); - -ALTER TABLE account.ldapConfig - ADD groupDn varchar(255) NULL; - -UPDATE account.ldapConfig SET groupDn = 'ou=groups,dc=verdnatura,dc=es'; - -DROP PROCEDURE IF EXISTS account.user_syncPassword; - -USE account; - -DELIMITER $$ - -CREATE TRIGGER role_beforeInsert - BEFORE INSERT ON `role` FOR EACH ROW -BEGIN - CALL role_checkName(NEW.`name`); -END$$ - -CREATE TRIGGER role_beforeUpdate - BEFORE UPDATE ON `role` FOR EACH ROW -BEGIN - IF !(NEW.`name` <=> OLD.`name`) THEN - CALL role_checkName (NEW.`name`); - END IF; -END$$ - -DELIMITER ; \ No newline at end of file diff --git a/db/changes/10221-accountModule/00-account.sql b/db/changes/10221-accountModule/00-account.sql new file mode 100644 index 000000000..d26d61c19 --- /dev/null +++ b/db/changes/10221-accountModule/00-account.sql @@ -0,0 +1,127 @@ + +ALTER TABLE `account`.`role` + MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL; + +ALTER TABLE `account`.`roleInherit` + ADD UNIQUE( `role`, `inheritsFrom`); + +ALTER TABLE `account`.`roleInherit` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`roleInherit` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + +ALTER TABLE `account`.`mailAlias` + ADD `description` VARCHAR(255) NULL AFTER `alias`; + +ALTER TABLE `account`.`mailAliasAccount` + ADD UNIQUE( `mailAlias`, `account`); + +ALTER TABLE `account`.`mailAliasAccount` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`mailAliasAccount` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + +ALTER TABLE account.ldapConfig + ADD groupDn varchar(255) NULL; + +UPDATE account.ldapConfig SET groupDn = 'ou=groups,dc=verdnatura,dc=es'; + +DROP PROCEDURE IF EXISTS account.user_syncPassword; + +ALTER TABLE account.`user` + MODIFY COLUMN sync tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Deprecated'; + +CREATE TABLE account.userSync ( + name varchar(30) NOT NULL, + CONSTRAINT userSync_PK PRIMARY KEY (name) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8 +COLLATE=utf8_general_ci; + +USE account; + +DELIMITER $$ + +DROP TRIGGER IF EXISTS account.user_beforeUpdate$$ +CREATE DEFINER=`root`@`%` TRIGGER `user_beforeUpdate` + BEFORE UPDATE ON `user` FOR EACH ROW +BEGIN + IF !(NEW.`name` <=> OLD.`name`) THEN + CALL user_checkName (NEW.`name`); + END IF; + + IF !(NEW.`password` <=> OLD.`password`) THEN + SET NEW.bcryptPassword = NULL; + SET NEW.lastPassChange = NOW(); + END IF; +END$$ + +DROP TRIGGER IF EXISTS account.user_afterUpdate$$ +CREATE DEFINER=`root`@`%` TRIGGER `user_afterUpdate` + AFTER UPDATE ON `user` FOR EACH ROW +BEGIN + INSERT IGNORE INTO userSync SET `name` = NEW.`name`; + + IF !(OLD.`name` <=> NEW.`name`) THEN + INSERT IGNORE INTO userSync SET `name` = OLD.`name`; + END IF; + + IF !(NEW.`role` <=> OLD.`role`) + THEN + INSERT INTO vn.mail SET + `sender` = 'jgallego@verdnatura.es', + `replyTo` = 'jgallego@verdnatura.es', + `subject` = 'Rol modificado', + `body` = CONCAT(myUserGetName(), ' ha modificado el rol del usuario ', + NEW.`name`, ' de ', OLD.role, ' a ', NEW.role); + END IF; +END$$ + +CREATE DEFINER=`root`@`%` TRIGGER `user_afterInsert` + AFTER INSERT ON `user` FOR EACH ROW +BEGIN + INSERT IGNORE INTO userSync SET `name` = NEW.`name`; +END$$ + +CREATE DEFINER=`root`@`%` TRIGGER `user_afterDelete` + AFTER DELETE ON `user` FOR EACH ROW +BEGIN + INSERT IGNORE INTO userSync SET `name` = OLD.`name`; +END$$ + +DROP TRIGGER IF EXISTS account.account_afterInsert$$ +CREATE DEFINER=`root`@`%` TRIGGER `account_afterInsert` + AFTER INSERT ON `account` FOR EACH ROW +BEGIN + INSERT IGNORE INTO userSync (`name`) + SELECT `name` FROM `user` WHERE id = NEW.id; +END$$ + +DROP TRIGGER IF EXISTS account.account_afterDelete$$ +CREATE DEFINER=`root`@`%` TRIGGER `account_afterDelete` + AFTER DELETE ON `account` FOR EACH ROW +BEGIN + INSERT IGNORE INTO userSync (`name`) + SELECT `name` FROM `user` WHERE id = OLD.id; +END$$ + +CREATE TRIGGER role_beforeInsert + BEFORE INSERT ON `role` FOR EACH ROW +BEGIN + CALL role_checkName(NEW.`name`); +END$$ + +CREATE TRIGGER role_beforeUpdate + BEFORE UPDATE ON `role` FOR EACH ROW +BEGIN + IF !(NEW.`name` <=> OLD.`name`) THEN + CALL role_checkName (NEW.`name`); + END IF; +END$$ + +DELIMITER ; \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-myUserChangePassword.sql b/db/changes/10221-accountModule/00-myUserChangePassword.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserChangePassword.sql rename to db/changes/10221-accountModule/00-myUserChangePassword.sql diff --git a/db/changes/10211-accountModule/00-myUserCheckLogin.sql b/db/changes/10221-accountModule/00-myUserCheckLogin.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserCheckLogin.sql rename to db/changes/10221-accountModule/00-myUserCheckLogin.sql diff --git a/db/changes/10211-accountModule/00-myUserGetId.sql b/db/changes/10221-accountModule/00-myUserGetId.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserGetId.sql rename to db/changes/10221-accountModule/00-myUserGetId.sql diff --git a/db/changes/10211-accountModule/00-myUserGetName.sql b/db/changes/10221-accountModule/00-myUserGetName.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserGetName.sql rename to db/changes/10221-accountModule/00-myUserGetName.sql diff --git a/db/changes/10211-accountModule/00-myUserHasRole.sql b/db/changes/10221-accountModule/00-myUserHasRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserHasRole.sql rename to db/changes/10221-accountModule/00-myUserHasRole.sql diff --git a/db/changes/10211-accountModule/00-myUserHasRoleId.sql b/db/changes/10221-accountModule/00-myUserHasRoleId.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUserHasRoleId.sql rename to db/changes/10221-accountModule/00-myUserHasRoleId.sql diff --git a/db/changes/10211-accountModule/00-myUser_changePassword.sql b/db/changes/10221-accountModule/00-myUser_changePassword.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_changePassword.sql rename to db/changes/10221-accountModule/00-myUser_changePassword.sql diff --git a/db/changes/10211-accountModule/00-myUser_checkLogin.sql b/db/changes/10221-accountModule/00-myUser_checkLogin.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_checkLogin.sql rename to db/changes/10221-accountModule/00-myUser_checkLogin.sql diff --git a/db/changes/10211-accountModule/00-myUser_getId.sql b/db/changes/10221-accountModule/00-myUser_getId.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_getId.sql rename to db/changes/10221-accountModule/00-myUser_getId.sql diff --git a/db/changes/10211-accountModule/00-myUser_getName.sql b/db/changes/10221-accountModule/00-myUser_getName.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_getName.sql rename to db/changes/10221-accountModule/00-myUser_getName.sql diff --git a/db/changes/10211-accountModule/00-myUser_hasRole.sql b/db/changes/10221-accountModule/00-myUser_hasRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_hasRole.sql rename to db/changes/10221-accountModule/00-myUser_hasRole.sql diff --git a/db/changes/10211-accountModule/00-myUser_hasRoleId.sql b/db/changes/10221-accountModule/00-myUser_hasRoleId.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_hasRoleId.sql rename to db/changes/10221-accountModule/00-myUser_hasRoleId.sql diff --git a/db/changes/10211-accountModule/00-myUser_login.sql b/db/changes/10221-accountModule/00-myUser_login.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_login.sql rename to db/changes/10221-accountModule/00-myUser_login.sql diff --git a/db/changes/10211-accountModule/00-myUser_loginWithKey.sql b/db/changes/10221-accountModule/00-myUser_loginWithKey.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_loginWithKey.sql rename to db/changes/10221-accountModule/00-myUser_loginWithKey.sql diff --git a/db/changes/10211-accountModule/00-myUser_loginWithName.sql b/db/changes/10221-accountModule/00-myUser_loginWithName.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_loginWithName.sql rename to db/changes/10221-accountModule/00-myUser_loginWithName.sql diff --git a/db/changes/10211-accountModule/00-myUser_logout.sql b/db/changes/10221-accountModule/00-myUser_logout.sql similarity index 100% rename from db/changes/10211-accountModule/00-myUser_logout.sql rename to db/changes/10221-accountModule/00-myUser_logout.sql diff --git a/db/changes/10211-accountModule/00-passwordGenerate.sql b/db/changes/10221-accountModule/00-passwordGenerate.sql similarity index 100% rename from db/changes/10211-accountModule/00-passwordGenerate.sql rename to db/changes/10221-accountModule/00-passwordGenerate.sql diff --git a/db/changes/10211-accountModule/00-role_checkName.sql b/db/changes/10221-accountModule/00-role_checkName.sql similarity index 100% rename from db/changes/10211-accountModule/00-role_checkName.sql rename to db/changes/10221-accountModule/00-role_checkName.sql diff --git a/db/changes/10211-accountModule/00-role_getDescendents.sql b/db/changes/10221-accountModule/00-role_getDescendents.sql similarity index 100% rename from db/changes/10211-accountModule/00-role_getDescendents.sql rename to db/changes/10221-accountModule/00-role_getDescendents.sql diff --git a/db/changes/10211-accountModule/00-role_sync.sql b/db/changes/10221-accountModule/00-role_sync.sql similarity index 100% rename from db/changes/10211-accountModule/00-role_sync.sql rename to db/changes/10221-accountModule/00-role_sync.sql diff --git a/db/changes/10211-accountModule/00-role_syncPrivileges.sql b/db/changes/10221-accountModule/00-role_syncPrivileges.sql similarity index 100% rename from db/changes/10211-accountModule/00-role_syncPrivileges.sql rename to db/changes/10221-accountModule/00-role_syncPrivileges.sql diff --git a/db/changes/10211-accountModule/00-userGetId.sql b/db/changes/10221-accountModule/00-userGetId.sql similarity index 100% rename from db/changes/10211-accountModule/00-userGetId.sql rename to db/changes/10221-accountModule/00-userGetId.sql diff --git a/db/changes/10211-accountModule/00-userGetMysqlRole.sql b/db/changes/10221-accountModule/00-userGetMysqlRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-userGetMysqlRole.sql rename to db/changes/10221-accountModule/00-userGetMysqlRole.sql diff --git a/db/changes/10211-accountModule/00-userGetName.sql b/db/changes/10221-accountModule/00-userGetName.sql similarity index 100% rename from db/changes/10211-accountModule/00-userGetName.sql rename to db/changes/10221-accountModule/00-userGetName.sql diff --git a/db/changes/10211-accountModule/00-userGetNameFromId.sql b/db/changes/10221-accountModule/00-userGetNameFromId.sql similarity index 100% rename from db/changes/10211-accountModule/00-userGetNameFromId.sql rename to db/changes/10221-accountModule/00-userGetNameFromId.sql diff --git a/db/changes/10211-accountModule/00-userHasRole.sql b/db/changes/10221-accountModule/00-userHasRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-userHasRole.sql rename to db/changes/10221-accountModule/00-userHasRole.sql diff --git a/db/changes/10211-accountModule/00-userHasRoleId.sql b/db/changes/10221-accountModule/00-userHasRoleId.sql similarity index 100% rename from db/changes/10211-accountModule/00-userHasRoleId.sql rename to db/changes/10221-accountModule/00-userHasRoleId.sql diff --git a/db/changes/10211-accountModule/00-userLogin.sql b/db/changes/10221-accountModule/00-userLogin.sql similarity index 100% rename from db/changes/10211-accountModule/00-userLogin.sql rename to db/changes/10221-accountModule/00-userLogin.sql diff --git a/db/changes/10211-accountModule/00-userLoginWithKey.sql b/db/changes/10221-accountModule/00-userLoginWithKey.sql similarity index 100% rename from db/changes/10211-accountModule/00-userLoginWithKey.sql rename to db/changes/10221-accountModule/00-userLoginWithKey.sql diff --git a/db/changes/10211-accountModule/00-userLoginWithName.sql b/db/changes/10221-accountModule/00-userLoginWithName.sql similarity index 100% rename from db/changes/10211-accountModule/00-userLoginWithName.sql rename to db/changes/10221-accountModule/00-userLoginWithName.sql diff --git a/db/changes/10211-accountModule/00-userLogout.sql b/db/changes/10221-accountModule/00-userLogout.sql similarity index 100% rename from db/changes/10211-accountModule/00-userLogout.sql rename to db/changes/10221-accountModule/00-userLogout.sql diff --git a/db/changes/10211-accountModule/00-userSetPassword.sql b/db/changes/10221-accountModule/00-userSetPassword.sql similarity index 100% rename from db/changes/10211-accountModule/00-userSetPassword.sql rename to db/changes/10221-accountModule/00-userSetPassword.sql diff --git a/db/changes/10211-accountModule/00-user_changePassword.sql b/db/changes/10221-accountModule/00-user_changePassword.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_changePassword.sql rename to db/changes/10221-accountModule/00-user_changePassword.sql diff --git a/db/changes/10211-accountModule/00-user_checkName.sql b/db/changes/10221-accountModule/00-user_checkName.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_checkName.sql rename to db/changes/10221-accountModule/00-user_checkName.sql diff --git a/db/changes/10211-accountModule/00-user_getMysqlRole.sql b/db/changes/10221-accountModule/00-user_getMysqlRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_getMysqlRole.sql rename to db/changes/10221-accountModule/00-user_getMysqlRole.sql diff --git a/db/changes/10211-accountModule/00-user_getNameFromId.sql b/db/changes/10221-accountModule/00-user_getNameFromId.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_getNameFromId.sql rename to db/changes/10221-accountModule/00-user_getNameFromId.sql diff --git a/db/changes/10211-accountModule/00-user_hasRole.sql b/db/changes/10221-accountModule/00-user_hasRole.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_hasRole.sql rename to db/changes/10221-accountModule/00-user_hasRole.sql diff --git a/db/changes/10211-accountModule/00-user_hasRoleId.sql b/db/changes/10221-accountModule/00-user_hasRoleId.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_hasRoleId.sql rename to db/changes/10221-accountModule/00-user_hasRoleId.sql diff --git a/db/changes/10211-accountModule/00-user_setPassword.sql b/db/changes/10221-accountModule/00-user_setPassword.sql similarity index 100% rename from db/changes/10211-accountModule/00-user_setPassword.sql rename to db/changes/10221-accountModule/00-user_setPassword.sql diff --git a/front/salix/components/layout/index.js b/front/salix/components/layout/index.js index b8ca74b99..c6bc80734 100644 --- a/front/salix/components/layout/index.js +++ b/front/salix/components/layout/index.js @@ -15,7 +15,7 @@ export class Layout extends Component { getUserData() { this.$http.get('Accounts/getCurrentUserData').then(json => { this.$.$root.user = json.data; - window.localStorage.currentUserWorkerId = json.data.workerId; + window.localStorage.currentUserWorkerId = json.data.id; }); } } diff --git a/front/salix/components/user-popover/index.html b/front/salix/components/user-popover/index.html index f34219d26..4b02edd9f 100644 --- a/front/salix/components/user-popover/index.html +++ b/front/salix/components/user-popover/index.html @@ -36,7 +36,7 @@ My account diff --git a/modules/account/back/methods/user-account/sync.js b/modules/account/back/methods/user-account/sync.js index 681f3674d..202411e35 100644 --- a/modules/account/back/methods/user-account/sync.js +++ b/modules/account/back/methods/user-account/sync.js @@ -28,11 +28,12 @@ module.exports = Self => { let $ = Self.app.models; let user = await $.Account.findOne({ - fields: ['id', 'sync'], + fields: ['id'], where: {name: userName} }); + let isSync = !await $.UserSync.exists(userName); - if (user && user.sync) return; + if (user && isSync) return; let accountConfig; let mailConfig; @@ -84,19 +85,18 @@ module.exports = Self => { if (user) { let bcryptPassword = $.User.hashPassword(password); - await $.Account.upsertWithWhere({id: user.id}, {bcryptPassword}); - await $.user.destroyById(user.id); - if (hasAccount) { - await $.user.upsert({ - id: user.id, - username: userName, - password: bcryptPassword, - email: user.email, - created: user.created, - updated: user.updated - }); - } + await $.Account.upsertWithWhere({id: user.id}, + {bcryptPassword} + ); + await $.user.upsert({ + id: user.id, + username: userName, + password: bcryptPassword, + email: user.email, + created: user.created, + updated: user.updated + }); } // SIP @@ -262,7 +262,6 @@ module.exports = Self => { // Mark as synchronized - // if (user) - // await $.Account.upsertWithWhere({id: user.id}, {sync: true}); + await $.UserSync.destroyById(userName); }; }; diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json index 9700ed0a1..d243a2cca 100644 --- a/modules/account/back/model-config.json +++ b/modules/account/back/model-config.json @@ -31,5 +31,8 @@ }, "UserPassword": { "dataSource": "vn" + }, + "UserSync": { + "dataSource": "vn" } } \ No newline at end of file diff --git a/modules/account/back/models/user-sync.json b/modules/account/back/models/user-sync.json new file mode 100644 index 000000000..60fd3fb3d --- /dev/null +++ b/modules/account/back/models/user-sync.json @@ -0,0 +1,15 @@ +{ + "name": "UserSync", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.userSync" + } + }, + "properties": { + "name": { + "type": "string", + "id": true + } + } +} diff --git a/modules/client/back/methods/client/specs/sendSms.spec.js b/modules/client/back/methods/client/specs/sendSms.spec.js index b299ac3c1..673a95cae 100644 --- a/modules/client/back/methods/client/specs/sendSms.spec.js +++ b/modules/client/back/methods/client/specs/sendSms.spec.js @@ -1,6 +1,7 @@ const app = require('vn-loopback/server/server'); -describe('client sendSms()', () => { +// Issue #2471 +xdescribe('client sendSms()', () => { let createdLog; afterAll(async done => { diff --git a/modules/client/back/methods/sms/send.spec.js b/modules/client/back/methods/sms/send.spec.js index 612a16cf1..06288ffb5 100644 --- a/modules/client/back/methods/sms/send.spec.js +++ b/modules/client/back/methods/sms/send.spec.js @@ -1,7 +1,8 @@ const app = require('vn-loopback/server/server'); const soap = require('soap'); -describe('sms send()', () => { +// Issue #2471 +xdescribe('sms send()', () => { it('should return the expected message and status code', async() => { const code = 200; const smsConfig = await app.models.SmsConfig.findOne(); diff --git a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js index 20066a5ba..a08e7555a 100644 --- a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js +++ b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js @@ -1,6 +1,7 @@ const app = require('vn-loopback/server/server'); -describe('ticket sendSms()', () => { +// Issue #2471 +xdescribe('ticket sendSms()', () => { let logId; afterAll(async done => { -- 2.40.1 From 1c621cff85b327fc2aea65773fe38ff1ec4bba15 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 21 Sep 2020 16:43:17 +0200 Subject: [PATCH 6/7] Fixtures fixes --- db/dump/fixtures.sql | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 36a1352b0..9074f3763 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -76,13 +76,15 @@ INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) INSERT INTO `account`.`mailAliasAccount`(`mailAlias`, `account`) VALUES - (1, 101), - (1, 102), - (2, 101); + (1, 1), + (1, 18), + (3, 18), + (1, 9), + (2, 9); INSERT INTO `account`.`mailForward`(`account`, `forwardTo`) VALUES - (101, 'employee@domain.local'); + (1, 'employee@domain.local'); INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) VALUES -- 2.40.1 From 5e056c315fbc8c7dd04c90f4822fb115a7cb28f2 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 21 Sep 2020 17:46:40 +0200 Subject: [PATCH 7/7] Tests fixed --- db/dump/dumpedFixtures.sql | 94 ++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index 5f869ab6f..374c1ed50 100644 --- a/db/dump/dumpedFixtures.sql +++ b/db/dump/dumpedFixtures.sql @@ -1,17 +1,18 @@ USE `util`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: util +-- Host: db.verdnatura.es Database: util -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -22,32 +23,34 @@ USE `util`; LOCK TABLES `config` WRITE; /*!40000 ALTER TABLE `config` DISABLE KEYS */; -INSERT INTO `config` VALUES (1,'10210',0,'test','2020-09-11 04:06:00'); +INSERT INTO `config` VALUES (1,'10210',0,'production',NULL); /*!40000 ALTER TABLE `config` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:00:58 +-- Dump completed on 2020-09-09 11:46:28 USE `account`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: account +-- Host: db.verdnatura.es Database: account -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -68,7 +71,7 @@ UNLOCK TABLES; LOCK TABLES `roleInherit` WRITE; /*!40000 ALTER TABLE `roleInherit` DISABLE KEYS */; -INSERT INTO `roleInherit` VALUES (1,2),(1,3),(1,70),(2,11),(3,11),(5,1),(5,21),(5,33),(9,0),(11,6),(13,1),(15,35),(15,57),(16,13),(16,15),(17,20),(17,37),(17,39),(17,64),(18,1),(19,21),(20,13),(20,16),(20,65),(21,13),(21,18),(21,53),(22,13),(22,21),(30,5),(30,20),(30,22),(30,53),(30,64),(31,1),(32,1),(34,1),(34,13),(34,33),(35,1),(36,44),(36,47),(37,1),(38,37),(38,64),(39,5),(39,21),(39,57),(40,1),(40,49),(41,13),(41,35),(41,40),(42,35),(42,49),(43,13),(43,42),(44,1),(45,13),(45,44),(47,1),(48,13),(48,47),(49,36),(49,58),(50,13),(50,21),(50,35),(50,49),(50,57),(50,59),(51,1),(52,13),(52,19),(52,35),(52,51),(53,1),(54,1),(55,13),(55,54),(56,1),(57,13),(57,56),(58,1),(59,13),(59,49),(60,5),(60,37),(60,50),(60,57),(61,13),(61,36),(65,19),(65,35),(65,50),(66,0),(67,5),(67,37),(69,35),(69,47),(70,11); +INSERT INTO `roleInherit` VALUES (9,0),(66,0),(5,1),(13,1),(18,1),(31,1),(32,1),(34,1),(35,1),(37,1),(40,1),(44,1),(47,1),(51,1),(53,1),(54,1),(56,1),(58,1),(1,2),(1,3),(30,5),(39,5),(60,5),(67,5),(11,6),(2,11),(3,11),(70,11),(16,13),(20,13),(21,13),(22,13),(34,13),(41,13),(43,13),(45,13),(48,13),(50,13),(52,13),(55,13),(57,13),(59,13),(61,13),(16,15),(20,16),(21,18),(52,19),(65,19),(17,20),(30,20),(5,21),(19,21),(22,21),(39,21),(50,21),(30,22),(5,33),(34,33),(15,35),(41,35),(42,35),(50,35),(52,35),(65,35),(69,35),(49,36),(61,36),(17,37),(38,37),(60,37),(67,37),(17,39),(41,40),(43,42),(36,44),(45,44),(36,47),(48,47),(69,47),(40,49),(42,49),(50,49),(59,49),(60,50),(65,50),(52,51),(21,53),(30,53),(55,54),(57,56),(15,57),(39,57),(50,57),(60,57),(49,58),(50,59),(17,64),(30,64),(38,64),(20,65),(1,70); /*!40000 ALTER TABLE `roleInherit` ENABLE KEYS */; UNLOCK TABLES; @@ -115,25 +118,27 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:00:59 +-- Dump completed on 2020-09-09 11:46:30 USE `salix`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: salix +-- Host: db.verdnatura.es Database: salix -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -161,25 +166,27 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:01:00 +-- Dump completed on 2020-09-09 11:46:30 USE `vn`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: vn +-- Host: db.verdnatura.es Database: vn -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -250,7 +257,7 @@ UNLOCK TABLES; LOCK TABLES `tag` WRITE; /*!40000 ALTER TABLE `tag` DISABLE KEYS */; -INSERT INTO `tag` VALUES (1,'color','Color',0,0,'ink',NULL,NULL,'inkFk'),(2,NULL,'Forma',1,0,NULL,NULL,NULL,NULL),(3,NULL,'Material',1,0,NULL,NULL,NULL,NULL),(4,NULL,'Longitud',1,1,NULL,'mm',NULL,'size'),(5,NULL,'Diámetro',1,1,NULL,'mm',NULL,'diameter'),(6,NULL,'Perímetro',1,1,NULL,'mm',NULL,NULL),(7,NULL,'Ancho de la base',1,1,NULL,'mm',NULL,NULL),(8,NULL,'Altura',1,1,NULL,'mm',NULL,'size'),(9,NULL,'Volumen',1,1,NULL,'ml',NULL,NULL),(10,NULL,'Densidad',1,1,NULL,NULL,NULL,NULL),(11,NULL,'Calidad',1,0,NULL,NULL,NULL,NULL),(12,NULL,'Textura',1,0,NULL,NULL,NULL,NULL),(13,NULL,'Material del mango',1,0,NULL,NULL,NULL,NULL),(14,NULL,'Compra mínima',1,0,NULL,NULL,NULL,NULL),(15,NULL,'Nº pétalos',1,1,NULL,NULL,NULL,NULL),(16,NULL,'Ancho',1,1,NULL,'mm',NULL,NULL),(18,NULL,'Profundidad',1,1,NULL,'mm',NULL,NULL),(19,NULL,'Largo',1,1,NULL,'mm',NULL,'size'),(20,NULL,'Ancho superior',1,1,NULL,'mm',NULL,NULL),(21,NULL,'Ancho inferior',1,1,NULL,'mm',NULL,NULL),(22,NULL,'Gramaje',1,1,NULL,'g',NULL,NULL),(23,'stems','Tallos',1,1,NULL,NULL,NULL,'stems'),(24,NULL,'Estado',1,0,NULL,NULL,NULL,NULL),(25,NULL,'Color principal',0,0,'ink',NULL,NULL,NULL),(26,NULL,'Color secundario',0,0,'ink',NULL,NULL,NULL),(27,NULL,'Longitud(cm)',1,1,NULL,'cm',NULL,NULL),(28,NULL,'Diámetro base',1,1,'','mm',NULL,'diameter'),(29,NULL,'Colección',1,0,NULL,NULL,NULL,NULL),(30,NULL,'Uds / caja',1,1,NULL,NULL,NULL,NULL),(31,NULL,'Contenido',1,0,NULL,NULL,NULL,NULL),(32,NULL,'Peso',1,1,NULL,'g',NULL,NULL),(33,NULL,'Grosor',1,1,NULL,'mm',NULL,NULL),(34,NULL,'Marca',1,0,NULL,NULL,NULL,NULL),(35,'origin','Origen',0,0,'origin',NULL,NULL,'originFk'),(36,NULL,'Proveedor',1,0,NULL,NULL,NULL,NULL),(37,'producer','Productor',0,0,'producer',NULL,NULL,'producerFk'),(38,NULL,'Duración',1,1,NULL,'s',NULL,NULL),(39,NULL,'Flor',1,0,NULL,NULL,NULL,NULL),(40,NULL,'Soporte',1,0,NULL,NULL,NULL,NULL),(41,NULL,'Tamaño flor',1,0,NULL,NULL,NULL,NULL),(42,NULL,'Apertura',1,0,NULL,NULL,NULL,NULL),(43,NULL,'Tallo',1,0,NULL,NULL,NULL,NULL),(44,NULL,'Nº hojas',1,1,NULL,NULL,NULL,NULL),(45,NULL,'Dimensiones',1,0,NULL,NULL,NULL,NULL),(46,NULL,'Diámetro boca',1,1,NULL,'mm',NULL,NULL),(47,NULL,'Nº flores',1,1,NULL,NULL,NULL,NULL),(48,NULL,'Uds / paquete',1,1,NULL,NULL,NULL,NULL),(49,NULL,'Maceta',1,1,NULL,'cm',NULL,'diameter'),(50,NULL,'Textura flor',1,0,NULL,NULL,NULL,NULL),(51,NULL,'Textura hoja',1,0,NULL,NULL,NULL,NULL),(52,NULL,'Tipo de IVA',1,0,NULL,NULL,NULL,NULL),(53,NULL,'Tronco',1,0,NULL,NULL,NULL,NULL),(54,NULL,'Hoja',1,0,NULL,NULL,NULL,NULL),(55,NULL,'Formato',1,0,NULL,NULL,NULL,NULL),(56,NULL,'Genero',1,0,NULL,NULL,NULL,NULL),(57,NULL,'Especie',1,0,NULL,NULL,NULL,NULL),(58,NULL,'Variedad',1,0,NULL,NULL,NULL,NULL),(59,NULL,'Medida grande',1,0,NULL,NULL,NULL,NULL),(60,NULL,'Medida mediano',1,0,NULL,NULL,NULL,NULL),(61,NULL,'Medida pequeño',1,0,NULL,NULL,NULL,NULL),(63,NULL,'Recipiente interior',1,0,NULL,NULL,NULL,NULL),(64,NULL,'Material secundario',1,0,NULL,NULL,NULL,NULL),(65,NULL,'Colores',1,0,NULL,NULL,NULL,NULL),(66,NULL,'Referencia',1,0,NULL,NULL,NULL,NULL),(67,'category','Categoria',1,0,NULL,NULL,NULL,NULL),(68,NULL,'Amb',1,0,NULL,NULL,NULL,NULL),(69,NULL,'Anchura',1,1,NULL,'cm',NULL,NULL),(70,NULL,'Hueco interior',1,0,NULL,NULL,NULL,NULL),(71,NULL,'Tamaño',1,0,NULL,NULL,NULL,NULL),(72,NULL,'Color botón',1,0,NULL,NULL,NULL,NULL),(73,NULL,'Tamaño minimo del botón',1,0,NULL,NULL,NULL,NULL),(74,NULL,'Obtentor',1,0,NULL,NULL,NULL,NULL),(75,NULL,'Longitud del brote',1,0,NULL,NULL,NULL,NULL),(76,NULL,'Tallos / u.v.',1,0,NULL,NULL,NULL,NULL),(77,NULL,'Madera de',1,0,NULL,NULL,NULL,NULL),(78,NULL,'Unidad de venta',1,0,NULL,NULL,NULL,NULL),(79,NULL,'Temporal',1,0,NULL,NULL,NULL,NULL),(80,NULL,'Gramaje/tallo',1,1,NULL,'g',NULL,NULL),(81,NULL,'Peso/paquete',1,1,NULL,'g',NULL,NULL),(82,NULL,'Flexibilidad del tallo',1,0,NULL,NULL,NULL,NULL),(83,NULL,'Nº planchas',1,1,NULL,NULL,NULL,NULL),(84,NULL,'Nº páginas',1,1,NULL,NULL,NULL,NULL),(85,NULL,'Editorial',1,0,NULL,NULL,NULL,NULL),(86,NULL,'Idioma',1,0,NULL,NULL,NULL,NULL),(87,NULL,'Fecha publicación',1,0,NULL,NULL,NULL,NULL),(88,NULL,'Cubierta',1,0,NULL,NULL,NULL,NULL),(89,NULL,'Encuadernación',1,0,NULL,NULL,NULL,NULL),(90,NULL,'Autor',1,0,NULL,NULL,NULL,NULL),(91,NULL,'Envoltorio',1,0,NULL,NULL,NULL,NULL),(92,NULL,'Nombre temporal',1,0,NULL,NULL,NULL,NULL),(93,NULL,'Modelo',1,0,NULL,NULL,NULL,NULL),(94,NULL,'Producto',1,0,NULL,NULL,NULL,NULL),(95,NULL,'Título',1,0,NULL,NULL,NULL,NULL),(96,NULL,'Tomo',1,0,NULL,NULL,NULL,NULL),(97,NULL,'Articulo',1,0,NULL,NULL,NULL,NULL),(98,NULL,'Metodo de cultivo',1,0,NULL,NULL,NULL,NULL),(99,NULL,'Edad',1,0,NULL,NULL,NULL,NULL),(100,NULL,'Agotado',1,0,NULL,NULL,NULL,NULL),(101,NULL,'Altura con asa',1,1,NULL,'cm',NULL,NULL),(102,NULL,'Nº tallos',1,1,NULL,NULL,NULL,NULL),(103,NULL,'Cultivo',1,0,NULL,NULL,NULL,NULL),(104,NULL,'Sabor',1,0,NULL,NULL,NULL,NULL),(105,NULL,'Talla',1,0,NULL,NULL,NULL,NULL),(106,NULL,'Calibre',1,1,NULL,NULL,NULL,NULL),(107,NULL,'Dulzura',1,1,NULL,'bx',NULL,NULL),(108,NULL,'Piezas',1,0,NULL,NULL,NULL,NULL),(109,NULL,'Altura con patas',1,0,NULL,NULL,NULL,NULL),(110,NULL,'Envase',1,0,NULL,NULL,NULL,NULL),(111,NULL,'Nº piezas',1,0,NULL,NULL,NULL,NULL),(112,NULL,'Uso',1,0,NULL,'cm',NULL,NULL),(113,NULL,'Color luz',1,0,NULL,NULL,NULL,NULL),(114,NULL,'Capacidad',1,0,NULL,NULL,NULL,NULL),(184,NULL,'Tallos por paquete',1,0,NULL,NULL,NULL,NULL),(205,NULL,'Apertura',1,0,NULL,NULL,'S05',NULL),(219,NULL,'Altura',1,0,NULL,NULL,'S20','size'),(552,NULL,'fout kenmerk',1,0,NULL,NULL,'081',NULL),(553,NULL,'Potinhoud',1,0,NULL,NULL,'A01',NULL),(554,NULL,'Marketingconcept',1,0,NULL,NULL,'A02',NULL),(555,NULL,'Leeftijd',1,0,NULL,NULL,'A03',NULL),(556,NULL,'Uitgangsmateriaal',1,0,NULL,NULL,'A04',NULL),(557,NULL,'Kleurbehandeld',1,0,NULL,NULL,'A05','inkFk'),(558,NULL,'Verzorging: Standplaats',1,0,NULL,NULL,'A06',NULL),(559,NULL,'Verzorging: Water',1,0,NULL,NULL,'A07',NULL),(560,NULL,'Verzorging: Voeding',1,0,NULL,NULL,'A08',NULL),(561,NULL,'Verzorging: Temperatuur',1,0,NULL,NULL,'A09',NULL),(562,NULL,'Verzorging: Specifieke in',1,0,NULL,NULL,'A10',NULL),(563,NULL,'Verzorging: Consumptie',1,0,NULL,NULL,'A11',NULL),(564,NULL,'Nabehandeling',1,0,NULL,NULL,'A13',NULL),(565,NULL,'Artikel beeld',1,0,NULL,NULL,'A23',NULL),(566,NULL,'Hoofdkleur 1',1,0,NULL,NULL,'B01',NULL),(567,NULL,'Hoofdkleur 2',1,0,NULL,NULL,'B02',NULL),(568,NULL,'RHS hoofdkleur 1',1,0,NULL,NULL,'B03',NULL),(569,NULL,'RHS hoofdkleur 2',1,0,NULL,NULL,'B04',NULL),(570,NULL,'Hoofdkleur 1 blad',1,0,NULL,NULL,'B05',NULL),(571,NULL,'Hoofdkleur 2 blad',1,0,NULL,NULL,'B06',NULL),(572,NULL,'RHS hoofdkleur 1 blad',1,0,NULL,NULL,'B07',NULL),(573,NULL,'RHS hoofdkleur 2 blad',1,0,NULL,NULL,'B08',NULL),(574,NULL,'Botanisch beeld',1,0,NULL,NULL,'B09',NULL),(575,NULL,'Hoofdkleur bes/vrucht',1,0,NULL,NULL,'B10',NULL),(576,NULL,'RHS hoofdkleur bes/vrucht',1,0,NULL,NULL,'B11',NULL),(577,NULL,'UPOV hoofdkleur 1 bloem',1,0,NULL,NULL,'B12',NULL),(578,NULL,'UPOV hoofdkleur 2 bloem',1,0,NULL,NULL,'B13',NULL),(579,NULL,'UPOV hoofdkleur 1 blad',1,0,NULL,NULL,'B14',NULL),(580,NULL,'UPOV hoofdkleur 2 blad',1,0,NULL,NULL,'B15',NULL),(581,NULL,'UPOV hoofdkleur bes/vruch',1,0,NULL,NULL,'B16',NULL),(582,NULL,'Negatieve keurcode 1',1,0,NULL,NULL,'K01',NULL),(583,NULL,'Negatieve keurcode 2',1,0,NULL,NULL,'K02',NULL),(584,NULL,'Bedrijfskenmerk fytosanit',1,0,NULL,NULL,'K03',NULL),(585,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K04',NULL),(586,NULL,'Certificaten MPS-TraceCer',1,0,NULL,NULL,'K05',NULL),(587,NULL,'Overige leveranciersinfor',1,0,NULL,NULL,'K07',NULL),(588,NULL,'Certificaten MPS-GAP',1,0,NULL,NULL,'K08',NULL),(589,NULL,'Betrouwbaarheidsindex kla',1,0,NULL,NULL,'K11',NULL),(590,NULL,'Betrouwbaarheidsindex waa',1,0,NULL,NULL,'K12',NULL),(591,NULL,'Productkwaliteitslabel',1,0,NULL,NULL,'K13',NULL),(592,NULL,'Label Fair Flowers Fair P',1,0,NULL,NULL,'K14',NULL),(593,NULL,'Certificaten Socialy Qual',1,0,NULL,NULL,'K15',NULL),(594,NULL,'Certificaten GlobalGAP',1,0,NULL,NULL,'K16',NULL),(595,NULL,'Certificaten MPS Quality',1,0,NULL,NULL,'K17',NULL),(596,NULL,'Certificaten biologisch',1,0,NULL,NULL,'K18',NULL),(597,NULL,'Certificaten eetbare prod',1,0,NULL,NULL,'K19',NULL),(598,NULL,'Certificaten Florimark',1,0,NULL,NULL,'K20',NULL),(599,NULL,'Certificaten Milieukeur',1,0,NULL,NULL,'K21',NULL),(600,NULL,'Certificaten Kenya Flower',1,0,NULL,NULL,'K22',NULL),(601,NULL,'Certificaten Fairtrade',1,0,NULL,NULL,'K23',NULL),(602,NULL,'Keurmerk MPS-ProductProof',1,0,NULL,NULL,'K24',NULL),(603,NULL,'Certificaten ISO',1,0,NULL,NULL,'K25',NULL),(604,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K26',NULL),(605,NULL,'Certificaten Florverde',1,0,NULL,NULL,'K27',NULL),(606,NULL,'Certificaten Ethical Trad',1,0,NULL,NULL,'K28',NULL),(607,NULL,'Certificaten Ethiopian EH',1,0,NULL,NULL,'K29',NULL),(608,NULL,'Certificaten gewasbescher',1,0,NULL,NULL,'K30',NULL),(609,NULL,'Certificaten SAN',1,0,NULL,NULL,'K31',NULL),(610,NULL,'Certificaten GRASP',1,0,NULL,NULL,'K32',NULL),(611,NULL,'Label Fair Flora',1,0,NULL,NULL,'K33',NULL),(612,NULL,'GLobalG.A.P. Chain of Cus',1,0,NULL,NULL,'K34',NULL),(613,NULL,'Fust',1,0,NULL,NULL,'L01',NULL),(614,NULL,'Stapelwagen',1,0,NULL,NULL,'L02',NULL),(615,NULL,'Aantal legborden veilings',1,0,NULL,NULL,'L03',NULL),(616,NULL,'Aantal legborden Deense s',1,0,NULL,NULL,'L04',NULL),(617,NULL,'Aantal onderstellen Deens',1,0,NULL,NULL,'L05',NULL),(618,NULL,'Fustsoort',1,0,NULL,NULL,'L06',NULL),(619,NULL,'Fustmateriaal',1,0,NULL,NULL,'L07',NULL),(620,NULL,'Aantal legborden Eurostap',1,0,NULL,NULL,'L08',NULL),(621,NULL,'Aantal onderstellen Euros',1,0,NULL,NULL,'L09',NULL),(622,NULL,'Tallos/bolsa',1,0,NULL,NULL,'L11',''),(623,NULL,'Aantal bossen per bundel',1,0,NULL,NULL,'L12',NULL),(624,NULL,'Aantal stuks per fust',1,0,NULL,NULL,'L13',NULL),(625,NULL,'Aantal bossen per fust',1,0,NULL,NULL,'L14',NULL),(626,NULL,'Aantal bundels per fust',1,0,NULL,NULL,'L15',NULL),(627,NULL,'Aantal bossen per hoes',1,0,NULL,NULL,'L16',NULL),(628,NULL,'Aantal bundels per hoes',1,0,NULL,NULL,'L17',NULL),(629,NULL,'Fustlabel',1,0,NULL,NULL,'L18',NULL),(630,NULL,'Karlabel',1,0,NULL,NULL,'L19',NULL),(631,NULL,'Service productlabel',1,0,NULL,NULL,'L20',NULL),(632,NULL,'Service fustlabel',1,0,NULL,NULL,'L21',NULL),(633,NULL,'Service karlabel',1,0,NULL,NULL,'L22',NULL),(634,NULL,'Aantal fusten per laag',1,0,NULL,NULL,'L23',NULL),(635,NULL,'Presentatie per schapm2',1,0,NULL,NULL,'L24',NULL),(636,NULL,'Positieve keurcode fytosa',1,0,NULL,NULL,'P01',NULL),(637,NULL,'Positieve keurcode kwalit',1,0,NULL,NULL,'P02',NULL),(638,NULL,'Positieve keurcode veilin',1,0,NULL,NULL,'P03',NULL),(639,NULL,'Maceta',1,1,NULL,'cm','S01','diameter'),(640,NULL,'Altura',1,0,NULL,NULL,'S02','size'),(641,NULL,'nº plantas',1,0,NULL,NULL,'S03',NULL),(642,NULL,'Diámetro',1,0,NULL,NULL,'S04',NULL),(644,NULL,'Combinatiehoogte',1,0,NULL,NULL,'S06',NULL),(645,NULL,'Plantas/Maceta',1,0,NULL,NULL,'S07',NULL),(646,NULL,'Dikte',1,0,NULL,NULL,'S08',NULL),(647,NULL,'nº flores',1,0,NULL,NULL,'S09',NULL),(648,NULL,'Min aantal bloemtrossen p',1,0,NULL,NULL,'S10',NULL),(649,NULL,'nº ramales',1,0,NULL,NULL,'S11',NULL),(650,NULL,'Minimum aantal bollen per',1,0,NULL,NULL,'S12',NULL),(651,NULL,'Minimum aantal bladeren p',1,0,NULL,NULL,'S13',NULL),(652,NULL,'Minimum stamhoogte',1,0,NULL,NULL,'S14',NULL),(653,NULL,'Altura caja',1,0,NULL,NULL,'S15',NULL),(654,NULL,'Lengte scheuten',1,0,NULL,NULL,'S16',NULL),(655,NULL,'Min aant vertakkingen pr ',1,0,NULL,NULL,'S17',NULL),(656,NULL,'Altura del capullo',1,0,NULL,NULL,'S19',NULL),(658,NULL,'Peso tallo',1,0,NULL,NULL,'S21',NULL),(659,NULL,'nº flores',1,0,NULL,NULL,'S22',NULL),(660,NULL,'Diámetro de la flor',1,0,NULL,NULL,'S23',NULL),(661,NULL,'Minimum bloemschedelengte',1,0,NULL,NULL,'S24',NULL),(662,NULL,'Aantal bloemkoppen per tr',1,0,NULL,NULL,'S25',NULL),(663,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S26',NULL),(664,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S27',NULL),(665,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S28',NULL),(666,NULL,'Longitud inflorescencia',1,0,NULL,NULL,'S29',NULL),(667,NULL,'Verpakkingswijze snijbloe',1,0,NULL,NULL,'S30',NULL),(668,NULL,'Minimum aant bloemen per ',1,0,NULL,NULL,'S31',NULL),(669,NULL,'Longitud',1,0,NULL,NULL,'S32',NULL),(670,NULL,'Jaartal sortering hout',1,0,NULL,NULL,'S33',NULL),(671,NULL,'Diámetro de la hoja',1,0,NULL,NULL,'S34',NULL),(672,NULL,'Peso paquete',1,0,NULL,NULL,'S35',NULL),(673,NULL,'Maximum planthoogte',1,0,NULL,NULL,'S36',NULL),(674,NULL,'Maximum plantdiameter',1,0,NULL,NULL,'S37',NULL),(675,NULL,'Max aantal bloemen/bloeiw',1,0,NULL,NULL,'S38',NULL),(676,NULL,'Maximum aantal takken per',1,0,NULL,NULL,'S39',NULL),(677,NULL,'Maximum aantal bollen per',1,0,NULL,NULL,'S40',NULL),(678,NULL,'Maximum stamhoogte',1,0,NULL,NULL,'S41',NULL),(679,NULL,'Longitud mínima',1,0,NULL,NULL,'S42','size'),(680,NULL,'Maximum aantal knoppen sn',1,0,NULL,NULL,'S43',NULL),(681,NULL,'Maximum bloemdiameter',1,0,NULL,NULL,'S44',NULL),(682,NULL,'Maximum bloeiwijzelengte',1,0,NULL,NULL,'S45',NULL),(683,NULL,'Aantal vruchten / trossen',1,0,NULL,NULL,'S46',NULL),(684,NULL,'Verpakkingswijze',1,0,NULL,NULL,'S47',NULL),(685,NULL,'Minimum vruchtdiameter',1,0,NULL,NULL,'S48',NULL),(686,NULL,'Bolomvang',1,0,NULL,NULL,'S49',NULL),(687,NULL,'Bloem/bes/vruchtkleur 1',1,0,NULL,NULL,'S50',NULL),(688,NULL,'Potvorm',1,0,NULL,NULL,'S51',NULL),(689,NULL,'Potkleur',1,0,NULL,NULL,'S52',NULL),(690,NULL,'Material maceta',1,0,NULL,NULL,'S53',NULL),(691,NULL,'Plantvorm',1,0,NULL,NULL,'S54',NULL),(692,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S55',NULL),(693,NULL,'Teeltwijze',1,0,NULL,NULL,'S56',NULL),(694,NULL,'Teeltmedium',1,0,NULL,NULL,'S57',NULL),(695,NULL,'Hoesmateriaal',1,0,NULL,NULL,'S58',NULL),(696,NULL,'Hoesvorm',1,0,NULL,NULL,'S59',NULL),(697,NULL,'Hoesbedrukking algemeen',1,0,NULL,NULL,'S60',NULL),(698,NULL,'Extra toevoegingen',1,0,NULL,NULL,'S61',NULL),(699,NULL,'Land van herkomst (bedrij',1,0,NULL,NULL,'S62',NULL),(700,NULL,'Verpakte orchidee',1,0,NULL,NULL,'S63',NULL),(701,NULL,'Hoesbedrukking extra',1,0,NULL,NULL,'S64',NULL),(702,NULL,'Voorbehandeling',1,0,NULL,NULL,'S65',NULL),(703,NULL,'Overige niet in pot',1,0,NULL,NULL,'S66',NULL),(704,NULL,'Vorm snijbloemen',1,0,NULL,NULL,'S67',NULL),(705,NULL,'Buigzaamheid bloemsteel',1,0,NULL,NULL,'S68',NULL),(706,NULL,'Hoeskleur',1,0,NULL,NULL,'S69',NULL),(707,NULL,'Extra deco materiaal',1,0,NULL,NULL,'S70',NULL),(708,NULL,'Productkleur',1,0,NULL,NULL,'S71','inkFk'),(709,NULL,'Productmateriaal',1,0,NULL,NULL,'S72',NULL),(710,NULL,'Materiaalhoogte',1,0,NULL,NULL,'S73',NULL),(711,NULL,'Materiaaldiameter',1,0,NULL,NULL,'S74',NULL),(712,NULL,'Barcode',1,0,NULL,NULL,'S75',NULL),(713,NULL,'Productlabel',1,0,NULL,NULL,'S76',NULL),(714,NULL,'Eetbaar/ niet eetbaar',1,0,NULL,NULL,'S77',NULL),(715,NULL,'Plantmaat zonder pot',1,0,NULL,NULL,'S78',NULL),(716,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S79',NULL),(717,NULL,'Maximum percentage oud ho',1,0,NULL,NULL,'S80',NULL),(718,NULL,'Maximum lengte verschil',1,0,NULL,NULL,'S81',NULL),(719,NULL,'Bladkleur',1,0,NULL,NULL,'S82',NULL),(720,NULL,'Plantgewicht',1,0,NULL,NULL,'S83',NULL),(721,NULL,'Diámetro',1,0,NULL,NULL,'S84',NULL),(722,NULL,'Bloem/bes/vruchtkleur 2',1,0,NULL,NULL,'S85',NULL),(723,NULL,'Winterhardheid (USDA zone',1,0,NULL,NULL,'S86',NULL),(724,NULL,'Kleurbehandeld',1,0,NULL,NULL,'S87','inkFk'),(725,NULL,'Bloem-/bladkleurverdeling',1,0,NULL,NULL,'S88',NULL),(726,NULL,'Diámetro del capullo',1,0,NULL,NULL,'S89',NULL),(727,NULL,'Volume inhoud',1,0,NULL,NULL,'S90',NULL),(728,NULL,'Vruchtbenaming',1,0,NULL,NULL,'S91',NULL),(729,NULL,'Vaaslevenindex',1,0,NULL,NULL,'S92',NULL),(730,NULL,'Overige informatie plante',1,0,NULL,NULL,'S93',NULL),(731,NULL,'Overige informatie snijbl',1,0,NULL,NULL,'S94',NULL),(732,NULL,'Toepassingsmogelijkheid',1,0,NULL,NULL,'S95',NULL),(733,NULL,'Productbeeld aanvoerder',1,0,NULL,NULL,'S96',NULL),(734,NULL,'MPS certificering',1,0,NULL,NULL,'S97',NULL),(735,NULL,'Kwaliteitsgroep',1,0,NULL,NULL,'S98',NULL),(736,NULL,'Artikelomschrijving',1,0,NULL,NULL,'S99',NULL),(737,NULL,'BTW-tarief',1,0,NULL,NULL,'T01',NULL),(738,NULL,'Prijseenheid',1,0,NULL,NULL,'T02',NULL),(739,NULL,'Transactievorm',1,0,NULL,NULL,'T03',NULL),(740,NULL,'Handelsverpakking voorwaa',1,0,NULL,NULL,'T10',NULL),(741,NULL,'Consumentenverpakking voo',1,0,NULL,NULL,'T11',NULL),(742,NULL,'Leveringsvoorwaarden',1,0,NULL,NULL,'T12',NULL),(743,NULL,'PT heffing voorwaarden',1,0,NULL,NULL,'T13',NULL),(744,NULL,'Serviceheffing voorwaarde',1,0,NULL,NULL,'T14',NULL),(745,NULL,'Algemene voorwaarden',1,0,NULL,NULL,'T15',NULL),(746,NULL,'Marktvorm',1,0,NULL,NULL,'T16',NULL),(747,NULL,'Themadagen',1,0,NULL,NULL,'T17',NULL),(748,NULL,'Handelscategorie',1,0,NULL,NULL,'T18',NULL),(749,NULL,'Producentengroepen',1,0,NULL,NULL,'T19',NULL),(750,NULL,'Favorieten Id',1,0,NULL,NULL,'T20',NULL),(751,NULL,'Verkoopeenheid',1,0,NULL,NULL,'T21',NULL),(752,NULL,'Veilgroep voorkeur',1,0,NULL,NULL,'V01',NULL),(753,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V02',NULL),(754,NULL,'Keurmeesternummer FloraHo',1,0,NULL,NULL,'V03',NULL),(755,NULL,'Rijnummer Rijnsburg',1,0,NULL,NULL,'V04',NULL),(756,NULL,'Verwerkingslocatie FloraH',1,0,NULL,NULL,'V05',NULL),(757,NULL,'FloraHolland Financial',1,0,NULL,NULL,'V06',NULL),(758,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V07',NULL),(759,NULL,'Benefiet veiling',1,0,NULL,NULL,'V08',NULL),(760,NULL,'Kloksoort',1,0,NULL,NULL,'V09',NULL),(761,NULL,'Minimumprijs aanvoerder',1,0,NULL,NULL,'V10',NULL),(762,NULL,'Rest aantallen',1,0,NULL,NULL,'V11',NULL),(763,NULL,'Veilsoort',1,0,NULL,NULL,'V12',NULL),(764,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V13',NULL),(765,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V14',NULL),(766,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V15',NULL),(767,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V16',NULL),(768,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V17',NULL),(769,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V18',NULL),(770,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V19',NULL),(771,NULL,'Gereserveerd',1,0,NULL,NULL,'V20',NULL),(772,NULL,'Veilgroep Aalsmeer',1,0,NULL,NULL,'V21',NULL),(773,NULL,'Promotie kenmerk FloraHol',1,0,NULL,NULL,'V22',NULL),(774,NULL,'Verrekening snijbloemenvo',1,0,NULL,NULL,'V23',NULL),(775,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V24',NULL),(776,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V25',NULL),(777,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V26',NULL),(778,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V27',NULL),(779,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V28',NULL),(780,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V29',NULL),(781,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V30',NULL),(782,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V31',NULL),(783,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V32',NULL),(784,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V33',NULL),(785,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V34',NULL),(786,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V35',NULL),(787,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V36',NULL),(788,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V37',NULL),(789,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V38',NULL),(790,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V39',NULL),(791,NULL,'Gereserveerd',1,0,NULL,NULL,'V40',NULL),(792,NULL,'Tussenopslag klok Plantio',1,0,NULL,NULL,'V41',NULL),(793,NULL,'Soort ladingsdrager Plant',1,0,NULL,NULL,'V42',NULL),(794,NULL,'Logistiek middel Plantion',1,0,NULL,NULL,'V43',NULL),(795,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V44',NULL),(796,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V45',NULL),(797,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V46',NULL),(798,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V47',NULL),(799,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V48',NULL),(800,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V49',NULL),(801,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V50',NULL),(802,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V51',NULL),(803,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V52',NULL),(804,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V53',NULL),(805,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V54',NULL),(806,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V55',NULL),(807,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V56',NULL),(808,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V57',NULL),(809,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V58',NULL),(810,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V59',NULL),(811,NULL,'Gereserveerd',1,0,NULL,NULL,'V60',NULL),(812,NULL,'Veilgroep Plantion Ede',1,0,NULL,NULL,'V61',NULL),(813,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V62',NULL),(814,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V63',NULL),(815,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V64',NULL),(816,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V65',NULL),(817,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V66',NULL),(818,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V67',NULL),(819,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V68',NULL),(820,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V69',NULL),(821,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V70',NULL),(822,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V71',NULL),(823,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V72',NULL),(824,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V73',NULL),(825,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V74',NULL),(826,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V75',NULL),(827,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V76',NULL),(828,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V77',NULL),(829,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V78',NULL),(830,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V79',NULL),(831,NULL,'Toegevoegde waardes VRM',1,0,NULL,NULL,'V80',NULL),(832,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V81',NULL),(833,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V82',NULL),(834,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V83',NULL),(835,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V84',NULL),(836,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V85',NULL),(837,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V86',NULL),(838,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V87',NULL),(839,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V88',NULL),(840,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V89',NULL),(841,NULL,'Veiling',1,0,NULL,NULL,'V99',NULL),(842,NULL,'kopersaantallen',1,0,NULL,NULL,'Z01',NULL),(843,NULL,'Caducidad',1,0,NULL,NULL,NULL,NULL),(844,NULL,'Lote',1,0,NULL,NULL,NULL,NULL),(845,NULL,'Uds palet',1,0,NULL,NULL,NULL,NULL); +INSERT INTO `tag` VALUES (1,'color','Color',0,0,'ink',NULL,NULL,'inkFk'),(2,NULL,'Forma',1,0,NULL,NULL,NULL,NULL),(3,NULL,'Material',1,0,NULL,NULL,NULL,NULL),(4,NULL,'Longitud',1,1,NULL,'mm',NULL,'size'),(5,NULL,'Diámetro',1,1,NULL,'mm',NULL,'diameter'),(6,NULL,'Perímetro',1,1,NULL,'mm',NULL,NULL),(7,NULL,'Ancho de la base',1,1,NULL,'mm',NULL,NULL),(8,NULL,'Altura',1,1,NULL,'mm',NULL,'size'),(9,NULL,'Volumen',1,1,NULL,'ml',NULL,NULL),(10,NULL,'Densidad',1,1,NULL,NULL,NULL,NULL),(11,NULL,'Calidad',1,0,NULL,NULL,NULL,NULL),(12,NULL,'Textura',1,0,NULL,NULL,NULL,NULL),(13,NULL,'Material del mango',1,0,NULL,NULL,NULL,NULL),(14,NULL,'Compra mínima',1,0,NULL,NULL,NULL,NULL),(15,NULL,'Nº pétalos',1,1,NULL,NULL,NULL,NULL),(16,NULL,'Ancho',1,1,NULL,'mm',NULL,NULL),(18,NULL,'Profundidad',1,1,NULL,'mm',NULL,NULL),(19,NULL,'Largo',1,1,NULL,'mm',NULL,'size'),(20,NULL,'Ancho superior',1,1,NULL,'mm',NULL,NULL),(21,NULL,'Ancho inferior',1,1,NULL,'mm',NULL,NULL),(22,NULL,'Gramaje',1,1,NULL,'g',NULL,NULL),(23,'stems','Tallos',1,1,NULL,NULL,NULL,'stems'),(24,NULL,'Estado',1,0,NULL,NULL,NULL,NULL),(25,NULL,'Color principal',0,0,'ink',NULL,NULL,NULL),(26,NULL,'Color secundario',0,0,'ink',NULL,NULL,NULL),(27,NULL,'Longitud(cm)',1,1,NULL,'cm',NULL,NULL),(28,NULL,'Diámetro base',1,1,'','mm',NULL,'diameter'),(29,NULL,'Colección',1,0,NULL,NULL,NULL,NULL),(30,NULL,'Uds / caja',1,1,NULL,NULL,NULL,NULL),(31,NULL,'Contenido',1,0,NULL,NULL,NULL,NULL),(32,NULL,'Peso',1,1,NULL,'g',NULL,NULL),(33,NULL,'Grosor',1,1,NULL,'mm',NULL,NULL),(34,NULL,'Marca',1,0,NULL,NULL,NULL,NULL),(35,'origin','Origen',0,0,'origin',NULL,NULL,'originFk'),(36,NULL,'Proveedor',1,0,NULL,NULL,NULL,NULL),(37,'producer','Productor',0,0,'producer',NULL,NULL,'producerFk'),(38,NULL,'Duración',1,1,NULL,'s',NULL,NULL),(39,NULL,'Flor',1,0,NULL,NULL,NULL,NULL),(40,NULL,'Soporte',1,0,NULL,NULL,NULL,NULL),(41,NULL,'Tamaño flor',1,0,NULL,NULL,NULL,NULL),(42,NULL,'Apertura',1,0,NULL,NULL,NULL,NULL),(43,NULL,'Tallo',1,0,NULL,NULL,NULL,NULL),(44,NULL,'Nº hojas',1,1,NULL,NULL,NULL,NULL),(45,NULL,'Dimensiones',1,0,NULL,NULL,NULL,NULL),(46,NULL,'Diámetro boca',1,1,NULL,'mm',NULL,NULL),(47,NULL,'Nº flores',1,1,NULL,NULL,NULL,NULL),(48,NULL,'Uds / paquete',1,1,NULL,NULL,NULL,NULL),(49,NULL,'Maceta',1,1,NULL,'cm',NULL,'diameter'),(50,NULL,'Textura flor',1,0,NULL,NULL,NULL,NULL),(51,NULL,'Textura hoja',1,0,NULL,NULL,NULL,NULL),(52,NULL,'Tipo de IVA',1,0,NULL,NULL,NULL,NULL),(53,NULL,'Tronco',1,0,NULL,NULL,NULL,NULL),(54,NULL,'Hoja',1,0,NULL,NULL,NULL,NULL),(55,NULL,'Formato',1,0,NULL,NULL,NULL,NULL),(56,NULL,'Genero',1,0,NULL,NULL,NULL,NULL),(57,NULL,'Especie',1,0,NULL,NULL,NULL,NULL),(58,NULL,'Variedad',1,0,NULL,NULL,NULL,NULL),(59,NULL,'Medida grande',1,0,NULL,NULL,NULL,NULL),(60,NULL,'Medida mediano',1,0,NULL,NULL,NULL,NULL),(61,NULL,'Medida pequeño',1,0,NULL,NULL,NULL,NULL),(63,NULL,'Recipiente interior',1,0,NULL,NULL,NULL,NULL),(64,NULL,'Material secundario',1,0,NULL,NULL,NULL,NULL),(65,NULL,'Colores',1,0,NULL,NULL,NULL,NULL),(66,NULL,'Referencia',1,0,NULL,NULL,NULL,NULL),(67,'category','Categoria',1,0,NULL,NULL,NULL,NULL),(68,NULL,'Amb',1,0,NULL,NULL,NULL,NULL),(69,NULL,'Anchura',1,1,NULL,'cm',NULL,NULL),(70,NULL,'Hueco interior',1,0,NULL,NULL,NULL,NULL),(71,NULL,'Tamaño',1,0,NULL,NULL,NULL,NULL),(72,NULL,'Color botón',1,0,NULL,NULL,NULL,NULL),(73,NULL,'Tamaño minimo del botón',1,0,NULL,NULL,NULL,NULL),(74,NULL,'Obtentor',1,0,NULL,NULL,NULL,NULL),(75,NULL,'Longitud del brote',1,0,NULL,NULL,NULL,NULL),(76,NULL,'Tallos / u.v.',1,0,NULL,NULL,NULL,NULL),(77,NULL,'Madera de',1,0,NULL,NULL,NULL,NULL),(78,NULL,'Unidad de venta',1,0,NULL,NULL,NULL,NULL),(79,NULL,'Temporal',1,0,NULL,NULL,NULL,NULL),(80,NULL,'Gramaje/tallo',1,1,NULL,'g',NULL,NULL),(81,NULL,'Peso/paquete',1,1,NULL,'g',NULL,NULL),(82,NULL,'Flexibilidad del tallo',1,0,NULL,NULL,NULL,NULL),(83,NULL,'Nº planchas',1,1,NULL,NULL,NULL,NULL),(84,NULL,'Nº páginas',1,1,NULL,NULL,NULL,NULL),(85,NULL,'Editorial',1,0,NULL,NULL,NULL,NULL),(86,NULL,'Idioma',1,0,NULL,NULL,NULL,NULL),(87,NULL,'Fecha publicación',1,0,NULL,NULL,NULL,NULL),(88,NULL,'Cubierta',1,0,NULL,NULL,NULL,NULL),(89,NULL,'Encuadernación',1,0,NULL,NULL,NULL,NULL),(90,NULL,'Autor',1,0,NULL,NULL,NULL,NULL),(91,NULL,'Envoltorio',1,0,NULL,NULL,NULL,NULL),(92,NULL,'Nombre temporal',1,0,NULL,NULL,NULL,NULL),(93,NULL,'Modelo',1,0,NULL,NULL,NULL,NULL),(94,NULL,'Producto',1,0,NULL,NULL,NULL,NULL),(95,NULL,'Título',1,0,NULL,NULL,NULL,NULL),(96,NULL,'Tomo',1,0,NULL,NULL,NULL,NULL),(97,NULL,'Articulo',1,0,NULL,NULL,NULL,NULL),(98,NULL,'Metodo de cultivo',1,0,NULL,NULL,NULL,NULL),(99,NULL,'Edad',1,0,NULL,NULL,NULL,NULL),(100,NULL,'Agotado',1,0,NULL,NULL,NULL,NULL),(101,NULL,'Altura con asa',1,1,NULL,'cm',NULL,NULL),(102,NULL,'Nº tallos',1,1,NULL,NULL,NULL,NULL),(103,NULL,'Cultivo',1,0,NULL,NULL,NULL,NULL),(104,NULL,'Sabor',1,0,NULL,NULL,NULL,NULL),(105,NULL,'Talla',1,0,NULL,NULL,NULL,NULL),(106,NULL,'Calibre',1,1,NULL,NULL,NULL,NULL),(107,NULL,'Dulzura',1,1,NULL,'bx',NULL,NULL),(108,NULL,'Piezas',1,0,NULL,NULL,NULL,NULL),(109,NULL,'Altura con patas',1,0,NULL,NULL,NULL,NULL),(110,NULL,'Envase',1,0,NULL,NULL,NULL,NULL),(111,NULL,'Nº piezas',1,0,NULL,NULL,NULL,NULL),(112,NULL,'Uso',1,0,NULL,'cm',NULL,NULL),(113,NULL,'Color luz',1,0,NULL,NULL,NULL,NULL),(114,NULL,'Capacidad',1,0,NULL,NULL,NULL,NULL),(184,NULL,'Tallos por paquete',1,0,NULL,NULL,NULL,NULL),(205,NULL,'Apertura',1,0,NULL,NULL,'S05',NULL),(219,NULL,'Altura',1,0,NULL,NULL,'S20','size'),(552,NULL,'fout kenmerk',1,0,NULL,NULL,'081',NULL),(553,NULL,'Potinhoud',1,0,NULL,NULL,'A01',NULL),(554,NULL,'Marketingconcept',1,0,NULL,NULL,'A02',NULL),(555,NULL,'Leeftijd',1,0,NULL,NULL,'A03',NULL),(556,NULL,'Uitgangsmateriaal',1,0,NULL,NULL,'A04',NULL),(557,NULL,'Kleurbehandeld',1,0,NULL,NULL,'A05','inkFk'),(558,NULL,'Verzorging: Standplaats',1,0,NULL,NULL,'A06',NULL),(559,NULL,'Verzorging: Water',1,0,NULL,NULL,'A07',NULL),(560,NULL,'Verzorging: Voeding',1,0,NULL,NULL,'A08',NULL),(561,NULL,'Verzorging: Temperatuur',1,0,NULL,NULL,'A09',NULL),(562,NULL,'Verzorging: Specifieke in',1,0,NULL,NULL,'A10',NULL),(563,NULL,'Verzorging: Consumptie',1,0,NULL,NULL,'A11',NULL),(564,NULL,'Nabehandeling',1,0,NULL,NULL,'A13',NULL),(565,NULL,'Artikel beeld',1,0,NULL,NULL,'A23',NULL),(566,NULL,'Hoofdkleur 1',1,0,NULL,NULL,'B01',NULL),(567,NULL,'Hoofdkleur 2',1,0,NULL,NULL,'B02',NULL),(568,NULL,'RHS hoofdkleur 1',1,0,NULL,NULL,'B03',NULL),(569,NULL,'RHS hoofdkleur 2',1,0,NULL,NULL,'B04',NULL),(570,NULL,'Hoofdkleur 1 blad',1,0,NULL,NULL,'B05',NULL),(571,NULL,'Hoofdkleur 2 blad',1,0,NULL,NULL,'B06',NULL),(572,NULL,'RHS hoofdkleur 1 blad',1,0,NULL,NULL,'B07',NULL),(573,NULL,'RHS hoofdkleur 2 blad',1,0,NULL,NULL,'B08',NULL),(574,NULL,'Botanisch beeld',1,0,NULL,NULL,'B09',NULL),(575,NULL,'Hoofdkleur bes/vrucht',1,0,NULL,NULL,'B10',NULL),(576,NULL,'RHS hoofdkleur bes/vrucht',1,0,NULL,NULL,'B11',NULL),(577,NULL,'UPOV hoofdkleur 1 bloem',1,0,NULL,NULL,'B12',NULL),(578,NULL,'UPOV hoofdkleur 2 bloem',1,0,NULL,NULL,'B13',NULL),(579,NULL,'UPOV hoofdkleur 1 blad',1,0,NULL,NULL,'B14',NULL),(580,NULL,'UPOV hoofdkleur 2 blad',1,0,NULL,NULL,'B15',NULL),(581,NULL,'UPOV hoofdkleur bes/vruch',1,0,NULL,NULL,'B16',NULL),(582,NULL,'Negatieve keurcode 1',1,0,NULL,NULL,'K01',NULL),(583,NULL,'Negatieve keurcode 2',1,0,NULL,NULL,'K02',NULL),(584,NULL,'Bedrijfskenmerk fytosanit',1,0,NULL,NULL,'K03',NULL),(585,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K04',NULL),(586,NULL,'Certificaten MPS-TraceCer',1,0,NULL,NULL,'K05',NULL),(587,NULL,'Overige leveranciersinfor',1,0,NULL,NULL,'K07',NULL),(588,NULL,'Certificaten MPS-GAP',1,0,NULL,NULL,'K08',NULL),(589,NULL,'Betrouwbaarheidsindex kla',1,0,NULL,NULL,'K11',NULL),(590,NULL,'Betrouwbaarheidsindex waa',1,0,NULL,NULL,'K12',NULL),(591,NULL,'Productkwaliteitslabel',1,0,NULL,NULL,'K13',NULL),(592,NULL,'Label Fair Flowers Fair P',1,0,NULL,NULL,'K14',NULL),(593,NULL,'Certificaten Socialy Qual',1,0,NULL,NULL,'K15',NULL),(594,NULL,'Certificaten GlobalGAP',1,0,NULL,NULL,'K16',NULL),(595,NULL,'Certificaten MPS Quality',1,0,NULL,NULL,'K17',NULL),(596,NULL,'Certificaten biologisch',1,0,NULL,NULL,'K18',NULL),(597,NULL,'Certificaten eetbare prod',1,0,NULL,NULL,'K19',NULL),(598,NULL,'Certificaten Florimark',1,0,NULL,NULL,'K20',NULL),(599,NULL,'Certificaten Milieukeur',1,0,NULL,NULL,'K21',NULL),(600,NULL,'Certificaten Kenya Flower',1,0,NULL,NULL,'K22',NULL),(601,NULL,'Certificaten Fairtrade',1,0,NULL,NULL,'K23',NULL),(602,NULL,'Keurmerk MPS-ProductProof',1,0,NULL,NULL,'K24',NULL),(603,NULL,'Certificaten ISO',1,0,NULL,NULL,'K25',NULL),(604,NULL,'Certificaten aardwarmte',1,0,NULL,NULL,'K26',NULL),(605,NULL,'Certificaten Florverde',1,0,NULL,NULL,'K27',NULL),(606,NULL,'Certificaten Ethical Trad',1,0,NULL,NULL,'K28',NULL),(607,NULL,'Certificaten Ethiopian EH',1,0,NULL,NULL,'K29',NULL),(608,NULL,'Certificaten gewasbescher',1,0,NULL,NULL,'K30',NULL),(609,NULL,'Certificaten SAN',1,0,NULL,NULL,'K31',NULL),(610,NULL,'Certificaten GRASP',1,0,NULL,NULL,'K32',NULL),(611,NULL,'Label Fair Flora',1,0,NULL,NULL,'K33',NULL),(612,NULL,'GLobalG.A.P. Chain of Cus',1,0,NULL,NULL,'K34',NULL),(613,NULL,'Fust',1,0,NULL,NULL,'L01',NULL),(614,NULL,'Stapelwagen',1,0,NULL,NULL,'L02',NULL),(615,NULL,'Aantal legborden veilings',1,0,NULL,NULL,'L03',NULL),(616,NULL,'Aantal legborden Deense s',1,0,NULL,NULL,'L04',NULL),(617,NULL,'Aantal onderstellen Deens',1,0,NULL,NULL,'L05',NULL),(618,NULL,'Fustsoort',1,0,NULL,NULL,'L06',NULL),(619,NULL,'Fustmateriaal',1,0,NULL,NULL,'L07',NULL),(620,NULL,'Aantal legborden Eurostap',1,0,NULL,NULL,'L08',NULL),(621,NULL,'Aantal onderstellen Euros',1,0,NULL,NULL,'L09',NULL),(622,NULL,'Tallos/bolsa',1,0,NULL,NULL,'L11',''),(623,NULL,'Aantal bossen per bundel',1,0,NULL,NULL,'L12',NULL),(624,NULL,'Aantal stuks per fust',1,0,NULL,NULL,'L13',NULL),(625,NULL,'Aantal bossen per fust',1,0,NULL,NULL,'L14',NULL),(626,NULL,'Aantal bundels per fust',1,0,NULL,NULL,'L15',NULL),(627,NULL,'Aantal bossen per hoes',1,0,NULL,NULL,'L16',NULL),(628,NULL,'Aantal bundels per hoes',1,0,NULL,NULL,'L17',NULL),(629,NULL,'Fustlabel',1,0,NULL,NULL,'L18',NULL),(630,NULL,'Karlabel',1,0,NULL,NULL,'L19',NULL),(631,NULL,'Service productlabel',1,0,NULL,NULL,'L20',NULL),(632,NULL,'Service fustlabel',1,0,NULL,NULL,'L21',NULL),(633,NULL,'Service karlabel',1,0,NULL,NULL,'L22',NULL),(634,NULL,'Aantal fusten per laag',1,0,NULL,NULL,'L23',NULL),(635,NULL,'Presentatie per schapm2',1,0,NULL,NULL,'L24',NULL),(636,NULL,'Positieve keurcode fytosa',1,0,NULL,NULL,'P01',NULL),(637,NULL,'Positieve keurcode kwalit',1,0,NULL,NULL,'P02',NULL),(638,NULL,'Positieve keurcode veilin',1,0,NULL,NULL,'P03',NULL),(639,NULL,'Maceta',1,1,NULL,'cm','S01','diameter'),(640,NULL,'Altura',1,0,NULL,NULL,'S02','size'),(641,NULL,'nº plantas',1,0,NULL,NULL,'S03',NULL),(642,NULL,'Diámetro',1,0,NULL,NULL,'S04',NULL),(644,NULL,'Combinatiehoogte',1,0,NULL,NULL,'S06',NULL),(645,NULL,'Plantas/Maceta',1,0,NULL,NULL,'S07',NULL),(646,NULL,'Dikte',1,0,NULL,NULL,'S08',NULL),(647,NULL,'nº flores',1,0,NULL,NULL,'S09',NULL),(648,NULL,'Min aantal bloemtrossen p',1,0,NULL,NULL,'S10',NULL),(649,NULL,'nº ramales',1,0,NULL,NULL,'S11',NULL),(650,NULL,'Minimum aantal bollen per',1,0,NULL,NULL,'S12',NULL),(651,NULL,'Minimum aantal bladeren p',1,0,NULL,NULL,'S13',NULL),(652,NULL,'Minimum stamhoogte',1,0,NULL,NULL,'S14',NULL),(653,NULL,'Altura caja',1,0,NULL,NULL,'S15',NULL),(654,NULL,'Lengte scheuten',1,0,NULL,NULL,'S16',NULL),(655,NULL,'Min aant vertakkingen pr ',1,0,NULL,NULL,'S17',NULL),(656,NULL,'Altura del capullo',1,0,NULL,NULL,'S19',NULL),(658,NULL,'Peso tallo',1,0,NULL,NULL,'S21',NULL),(659,NULL,'nº flores',1,0,NULL,NULL,'S22',NULL),(660,NULL,'Diámetro de la flor',1,0,NULL,NULL,'S23',NULL),(661,NULL,'Minimum bloemschedelengte',1,0,NULL,NULL,'S24',NULL),(662,NULL,'Aantal bloemkoppen per tr',1,0,NULL,NULL,'S25',NULL),(663,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S26',NULL),(664,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S27',NULL),(665,NULL,'Aant.kleuren/cultiv/vorme',1,0,NULL,NULL,'S28',NULL),(666,NULL,'Longitud inflorescencia',1,0,NULL,NULL,'S29',NULL),(667,NULL,'Verpakkingswijze snijbloe',1,0,NULL,NULL,'S30',NULL),(668,NULL,'Minimum aant bloemen per ',1,0,NULL,NULL,'S31',NULL),(669,NULL,'Longitud',1,0,NULL,NULL,'S32',NULL),(670,NULL,'Jaartal sortering hout',1,0,NULL,NULL,'S33',NULL),(671,NULL,'Diámetro de la hoja',1,0,NULL,NULL,'S34',NULL),(672,NULL,'Peso paquete',1,0,NULL,NULL,'S35',NULL),(673,NULL,'Maximum planthoogte',1,0,NULL,NULL,'S36',NULL),(674,NULL,'Maximum plantdiameter',1,0,NULL,NULL,'S37',NULL),(675,NULL,'Max aantal bloemen/bloeiw',1,0,NULL,NULL,'S38',NULL),(676,NULL,'Maximum aantal takken per',1,0,NULL,NULL,'S39',NULL),(677,NULL,'Maximum aantal bollen per',1,0,NULL,NULL,'S40',NULL),(678,NULL,'Maximum stamhoogte',1,0,NULL,NULL,'S41',NULL),(679,NULL,'Longitud mínima',1,0,NULL,NULL,'S42','size'),(680,NULL,'Maximum aantal knoppen sn',1,0,NULL,NULL,'S43',NULL),(681,NULL,'Maximum bloemdiameter',1,0,NULL,NULL,'S44',NULL),(682,NULL,'Maximum bloeiwijzelengte',1,0,NULL,NULL,'S45',NULL),(683,NULL,'Aantal vruchten / trossen',1,0,NULL,NULL,'S46',NULL),(684,NULL,'Verpakkingswijze',1,0,NULL,NULL,'S47',NULL),(685,NULL,'Minimum vruchtdiameter',1,0,NULL,NULL,'S48',NULL),(686,NULL,'Bolomvang',1,0,NULL,NULL,'S49',NULL),(687,NULL,'Bloem/bes/vruchtkleur 1',1,0,NULL,NULL,'S50',NULL),(688,NULL,'Potvorm',1,0,NULL,NULL,'S51',NULL),(689,NULL,'Potkleur',1,0,NULL,NULL,'S52',NULL),(690,NULL,'Material maceta',1,0,NULL,NULL,'S53',NULL),(691,NULL,'Plantvorm',1,0,NULL,NULL,'S54',NULL),(692,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S55',NULL),(693,NULL,'Teeltwijze',1,0,NULL,NULL,'S56',NULL),(694,NULL,'Teeltmedium',1,0,NULL,NULL,'S57',NULL),(695,NULL,'Hoesmateriaal',1,0,NULL,NULL,'S58',NULL),(696,NULL,'Hoesvorm',1,0,NULL,NULL,'S59',NULL),(697,NULL,'Hoesbedrukking algemeen',1,0,NULL,NULL,'S60',NULL),(698,NULL,'Extra toevoegingen',1,0,NULL,NULL,'S61',NULL),(699,NULL,'Land van herkomst (bedrij',1,0,NULL,NULL,'S62',NULL),(700,NULL,'Verpakte orchidee',1,0,NULL,NULL,'S63',NULL),(701,NULL,'Hoesbedrukking extra',1,0,NULL,NULL,'S64',NULL),(702,NULL,'Voorbehandeling',1,0,NULL,NULL,'S65',NULL),(703,NULL,'Overige niet in pot',1,0,NULL,NULL,'S66',NULL),(704,NULL,'Vorm snijbloemen',1,0,NULL,NULL,'S67',NULL),(705,NULL,'Buigzaamheid bloemsteel',1,0,NULL,NULL,'S68',NULL),(706,NULL,'Hoeskleur',1,0,NULL,NULL,'S69',NULL),(707,NULL,'Extra deco materiaal',1,0,NULL,NULL,'S70',NULL),(708,NULL,'Productkleur',1,0,NULL,NULL,'S71','inkFk'),(709,NULL,'Productmateriaal',1,0,NULL,NULL,'S72',NULL),(710,NULL,'Materiaalhoogte',1,0,NULL,NULL,'S73',NULL),(711,NULL,'Materiaaldiameter',1,0,NULL,NULL,'S74',NULL),(712,NULL,'Barcode',1,0,NULL,NULL,'S75',NULL),(713,NULL,'Productlabel',1,0,NULL,NULL,'S76',NULL),(714,NULL,'Eetbaar/ niet eetbaar',1,0,NULL,NULL,'S77',NULL),(715,NULL,'Plantmaat zonder pot',1,0,NULL,NULL,'S78',NULL),(716,NULL,'Aantal kleuren/cultiv per',1,0,NULL,NULL,'S79',NULL),(717,NULL,'Maximum percentage oud ho',1,0,NULL,NULL,'S80',NULL),(718,NULL,'Maximum lengte verschil',1,0,NULL,NULL,'S81',NULL),(719,NULL,'Bladkleur',1,0,NULL,NULL,'S82',NULL),(720,NULL,'Plantgewicht',1,0,NULL,NULL,'S83',NULL),(721,NULL,'Diámetro',1,0,NULL,NULL,'S84',NULL),(722,NULL,'Bloem/bes/vruchtkleur 2',1,0,NULL,NULL,'S85',NULL),(723,NULL,'Winterhardheid (USDA zone',1,0,NULL,NULL,'S86',NULL),(724,NULL,'Kleurbehandeld',1,0,NULL,NULL,'S87','inkFk'),(725,NULL,'Bloem-/bladkleurverdeling',1,0,NULL,NULL,'S88',NULL),(726,NULL,'Diámetro del capullo',1,0,NULL,NULL,'S89',NULL),(727,NULL,'Volume inhoud',1,0,NULL,NULL,'S90',NULL),(728,NULL,'Vruchtbenaming',1,0,NULL,NULL,'S91',NULL),(729,NULL,'Vaaslevenindex',1,0,NULL,NULL,'S92',NULL),(730,NULL,'Overige informatie plante',1,0,NULL,NULL,'S93',NULL),(731,NULL,'Overige informatie snijbl',1,0,NULL,NULL,'S94',NULL),(732,NULL,'Toepassingsmogelijkheid',1,0,NULL,NULL,'S95',NULL),(733,NULL,'Productbeeld aanvoerder',1,0,NULL,NULL,'S96',NULL),(734,NULL,'MPS certificering',1,0,NULL,NULL,'S97',NULL),(735,NULL,'Kwaliteitsgroep',1,0,NULL,NULL,'S98',NULL),(736,NULL,'Artikelomschrijving',1,0,NULL,NULL,'S99',NULL),(737,NULL,'BTW-tarief',1,0,NULL,NULL,'T01',NULL),(738,NULL,'Prijseenheid',1,0,NULL,NULL,'T02',NULL),(739,NULL,'Transactievorm',1,0,NULL,NULL,'T03',NULL),(740,NULL,'Handelsverpakking voorwaa',1,0,NULL,NULL,'T10',NULL),(741,NULL,'Consumentenverpakking voo',1,0,NULL,NULL,'T11',NULL),(742,NULL,'Leveringsvoorwaarden',1,0,NULL,NULL,'T12',NULL),(743,NULL,'PT heffing voorwaarden',1,0,NULL,NULL,'T13',NULL),(744,NULL,'Serviceheffing voorwaarde',1,0,NULL,NULL,'T14',NULL),(745,NULL,'Algemene voorwaarden',1,0,NULL,NULL,'T15',NULL),(746,NULL,'Marktvorm',1,0,NULL,NULL,'T16',NULL),(747,NULL,'Themadagen',1,0,NULL,NULL,'T17',NULL),(748,NULL,'Handelscategorie',1,0,NULL,NULL,'T18',NULL),(749,NULL,'Producentengroepen',1,0,NULL,NULL,'T19',NULL),(750,NULL,'Favorieten Id',1,0,NULL,NULL,'T20',NULL),(751,NULL,'Verkoopeenheid',1,0,NULL,NULL,'T21',NULL),(752,NULL,'Veilgroep voorkeur',1,0,NULL,NULL,'V01',NULL),(753,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V02',NULL),(754,NULL,'Keurmeesternummer FloraHo',1,0,NULL,NULL,'V03',NULL),(755,NULL,'Rijnummer Rijnsburg',1,0,NULL,NULL,'V04',NULL),(756,NULL,'Verwerkingslocatie FloraH',1,0,NULL,NULL,'V05',NULL),(757,NULL,'FloraHolland Financial',1,0,NULL,NULL,'V06',NULL),(758,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V07',NULL),(759,NULL,'Benefiet veiling',1,0,NULL,NULL,'V08',NULL),(760,NULL,'Kloksoort',1,0,NULL,NULL,'V09',NULL),(761,NULL,'Minimumprijs aanvoerder',1,0,NULL,NULL,'V10',NULL),(762,NULL,'Rest aantallen',1,0,NULL,NULL,'V11',NULL),(763,NULL,'Veilsoort',1,0,NULL,NULL,'V12',NULL),(764,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V13',NULL),(765,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V14',NULL),(766,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V15',NULL),(767,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V16',NULL),(768,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V17',NULL),(769,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V18',NULL),(770,NULL,'Gereserveerd FloraHolland',1,0,NULL,NULL,'V19',NULL),(771,NULL,'Gereserveerd',1,0,NULL,NULL,'V20',NULL),(772,NULL,'Veilgroep Aalsmeer',1,0,NULL,NULL,'V21',NULL),(773,NULL,'Promotie kenmerk FloraHol',1,0,NULL,NULL,'V22',NULL),(774,NULL,'Verrekening snijbloemenvo',1,0,NULL,NULL,'V23',NULL),(775,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V24',NULL),(776,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V25',NULL),(777,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V26',NULL),(778,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V27',NULL),(779,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V28',NULL),(780,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V29',NULL),(781,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V30',NULL),(782,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V31',NULL),(783,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V32',NULL),(784,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V33',NULL),(785,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V34',NULL),(786,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V35',NULL),(787,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V36',NULL),(788,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V37',NULL),(789,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V38',NULL),(790,NULL,'Gereserveerd Aalsmeer',1,0,NULL,NULL,'V39',NULL),(791,NULL,'Gereserveerd',1,0,NULL,NULL,'V40',NULL),(792,NULL,'Tussenopslag klok Plantio',1,0,NULL,NULL,'V41',NULL),(793,NULL,'Soort ladingsdrager Plant',1,0,NULL,NULL,'V42',NULL),(794,NULL,'Logistiek middel Plantion',1,0,NULL,NULL,'V43',NULL),(795,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V44',NULL),(796,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V45',NULL),(797,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V46',NULL),(798,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V47',NULL),(799,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V48',NULL),(800,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V49',NULL),(801,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V50',NULL),(802,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V51',NULL),(803,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V52',NULL),(804,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V53',NULL),(805,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V54',NULL),(806,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V55',NULL),(807,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V56',NULL),(808,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V57',NULL),(809,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V58',NULL),(810,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V59',NULL),(811,NULL,'Gereserveerd',1,0,NULL,NULL,'V60',NULL),(812,NULL,'Veilgroep Plantion Ede',1,0,NULL,NULL,'V61',NULL),(813,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V62',NULL),(814,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V63',NULL),(815,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V64',NULL),(816,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V65',NULL),(817,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V66',NULL),(818,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V67',NULL),(819,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V68',NULL),(820,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V69',NULL),(821,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V70',NULL),(822,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V71',NULL),(823,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V72',NULL),(824,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V73',NULL),(825,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V74',NULL),(826,NULL,'Gereserveerd Plantion Ede',1,0,NULL,NULL,'V75',NULL),(827,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V76',NULL),(828,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V77',NULL),(829,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V78',NULL),(830,NULL,'Gereserveerd Holambra',1,0,NULL,NULL,'V79',NULL),(831,NULL,'Toegevoegde waardes VRM',1,0,NULL,NULL,'V80',NULL),(832,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V81',NULL),(833,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V82',NULL),(834,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V83',NULL),(835,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V84',NULL),(836,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V85',NULL),(837,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V86',NULL),(838,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V87',NULL),(839,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V88',NULL),(840,NULL,'Gereserveerd VRM',1,0,NULL,NULL,'V89',NULL),(841,NULL,'Veiling',1,0,NULL,NULL,'V99',NULL),(842,NULL,'kopersaantallen',1,0,NULL,NULL,'Z01',NULL),(843,NULL,'Caducidad',1,0,NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `tag` ENABLE KEYS */; UNLOCK TABLES; @@ -310,7 +317,7 @@ UNLOCK TABLES; LOCK TABLES `state` WRITE; /*!40000 ALTER TABLE `state` DISABLE KEYS */; -INSERT INTO `state` VALUES (1,'Arreglar',2,0,'FIXING',NULL,1,0,0,0,0,0,0,4),(2,'Libre',2,0,'FREE',NULL,2,1,0,0,0,1,0,4),(3,'OK',3,0,'OK',3,28,1,0,0,0,1,1,3),(4,'Impreso',4,0,'PRINTED',2,29,1,0,1,0,0,0,2),(5,'Preparación',5,1,'ON_PREPARATION',7,5,0,0,0,2,0,0,2),(6,'En Revisión',7,1,'ON_CHECKING',NULL,6,0,1,0,3,0,0,1),(7,'Sin Acabar',1,0,'NOT_READY',NULL,7,0,0,0,0,1,0,4),(8,'Revisado',8,1,'CHECKED',NULL,8,0,1,0,3,0,0,1),(9,'Encajando',9,2,'PACKING',NULL,9,0,1,0,0,0,0,0),(10,'Encajado',10,2,'PACKED',NULL,10,0,1,0,0,0,0,0),(11,'Facturado',0,3,'INVOICED',NULL,11,0,1,0,0,0,0,0),(12,'Bloqueado',0,0,'BLOCKED',NULL,12,0,0,0,0,0,0,4),(13,'En Reparto',11,3,'ON_DELIVERY',NULL,13,0,1,0,0,0,0,0),(14,'Preparado',6,1,'PREPARED',NULL,14,0,1,0,2,0,0,1),(15,'Pte Recogida',12,3,'WAITING_FOR_PICKUP',NULL,15,0,1,0,0,0,0,0),(16,'Entregado',13,3,'DELIVERED',NULL,16,0,1,0,0,0,0,0),(20,'Asignado',4,0,'PICKER_DESIGNED',NULL,20,1,0,0,0,0,0,2),(21,'Retornado',4,1,'PRINTED_BACK',6,21,0,0,0,0,0,0,2),(22,'¿Fecha?',2,0,'WRONG_DATE',NULL,22,0,0,0,0,0,0,4),(23,'URGENTE',2,0,'LAST_CALL',NULL,23,1,0,0,0,0,0,4),(24,'Encadenado',4,0,'CHAINED',4,24,0,0,0,0,0,0,3),(25,'Embarcando',3,0,'BOARDING',5,25,1,0,0,0,1,0,3),(26,'Prep Previa',5,1,'PREVIOUS_PREPARATION',1,26,0,0,0,1,0,0,2),(27,'Prep Asistida',5,1,'ASSISTED_PREPARATION',7,27,0,0,0,0,0,0,2),(28,'Previa OK',3,1,'OK PREVIOUS',3,28,1,0,0,1,1,1,3),(29,'Previa Impreso',4,1,'PRINTED PREVIOUS',2,29,1,0,1,1,0,0,3),(30,'Embarcado',4,1,'BOARD',5,30,0,0,0,2,0,0,3),(31,'Polizon Impreso',4,1,'PRINTED STOWAWAY',2,29,1,0,1,0,0,0,3),(32,'Polizon OK',3,1,'OK STOWAWAY',3,31,1,0,0,1,1,1,3),(33,'Auto_Impreso',4,0,'PRINTED_AUTO',2,29,1,0,1,0,0,0,2); +INSERT INTO `state` VALUES (1,'Arreglar',2,0,'FIXING',NULL,1,0,0,0,0,0,0,4),(2,'Libre',2,0,'FREE',NULL,2,1,0,0,0,1,0,4),(3,'OK',3,0,'OK',3,28,1,0,0,0,1,1,3),(4,'Impreso',4,1,'PRINTED',2,29,1,0,1,0,0,0,2),(5,'Preparación',5,1,'ON_PREPARATION',7,5,0,0,0,2,0,0,2),(6,'En Revisión',7,1,'ON_CHECKING',NULL,6,0,1,0,3,0,0,1),(7,'Sin Acabar',1,0,'NOT_READY',NULL,7,0,0,0,0,1,0,4),(8,'Revisado',8,1,'CHECKED',NULL,8,0,1,0,3,0,0,1),(9,'Encajando',9,2,'PACKING',NULL,9,0,1,0,0,0,0,0),(10,'Encajado',10,2,'PACKED',NULL,10,0,1,0,0,0,0,0),(11,'Facturado',0,0,'INVOICED',NULL,11,0,1,0,0,0,0,0),(12,'Bloqueado',0,0,'BLOCKED',NULL,12,0,0,0,0,0,0,4),(13,'En Reparto',11,3,'ON_DELIVERY',NULL,13,0,1,0,0,0,0,0),(14,'Preparado',6,1,'PREPARED',NULL,14,0,1,0,2,0,0,1),(15,'Pte Recogida',12,3,'WAITING_FOR_PICKUP',NULL,15,0,1,0,0,0,0,0),(16,'Entregado',13,3,'DELIVERED',NULL,16,0,1,0,0,0,0,0),(20,'Asignado',4,1,'PICKER_DESIGNED',NULL,20,1,0,0,0,0,0,2),(21,'Retornado',4,1,'PRINTED_BACK',6,21,0,0,0,0,0,0,2),(22,'¿Fecha?',2,0,'WRONG_DATE',NULL,22,0,0,0,0,0,0,4),(23,'URGENTE',2,0,'LAST_CALL',NULL,23,1,0,0,0,0,0,4),(24,'Encadenado',4,0,'CHAINED',4,24,0,0,0,0,0,0,3),(25,'Embarcando',3,0,'BOARDING',5,25,1,0,0,0,1,0,3),(26,'Prep Previa',5,1,'PREVIOUS_PREPARATION',1,26,0,0,0,1,0,0,2),(27,'Prep Asistida',5,1,'ASSISTED_PREPARATION',7,27,0,0,0,0,0,0,2),(28,'Previa OK',3,1,'OK PREVIOUS',3,28,1,0,0,1,1,1,3),(29,'Previa Impreso',4,1,'PRINTED PREVIOUS',2,29,1,0,1,1,0,0,3),(30,'Embarcado',4,0,'BOARD',5,30,0,0,0,2,0,0,3),(31,'Polizon Impreso',4,1,'PRINTED STOWAWAY',2,29,1,0,1,0,0,0,3),(32,'Polizon OK',3,1,'OK STOWAWAY',3,31,1,0,0,1,1,1,3),(33,'Auto_Impreso',4,1,'PRINTED_AUTO',2,29,1,0,1,0,0,0,2); /*!40000 ALTER TABLE `state` ENABLE KEYS */; UNLOCK TABLES; @@ -330,7 +337,7 @@ UNLOCK TABLES; LOCK TABLES `department` WRITE; /*!40000 ALTER TABLE `department` DISABLE KEYS */; -INSERT INTO `department` VALUES (1,'VERDNATURA',1,2,763,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(22,'COMPRAS',3,4,NULL,72,596,2,5,0,0,0,0,NULL,'/',NULL,1),(23,'CAMARA',14,19,NULL,72,604,2,6,1,0,1,2,37,'/37/',NULL,0),(31,'INFORMATICA',5,6,NULL,72,127,3,9,0,0,0,0,NULL,'/','informatica',1),(34,'CONTABILIDAD',7,8,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(35,'FINANZAS',9,10,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(36,'LABORAL',11,12,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(37,'PRODUCCION',13,52,NULL,72,230,3,11,1,0,0,17,NULL,'/',NULL,0),(38,'SACADO',20,21,NULL,72,230,4,14,1,0,1,0,37,'/37/',NULL,0),(39,'ENCAJADO',22,23,NULL,72,230,4,12,1,0,1,0,37,'/37/',NULL,0),(41,'ADMINISTRACION',53,54,NULL,72,599,3,8,0,0,0,0,NULL,'/',NULL,1),(43,'VENTAS',55,76,NULL,0,NULL,NULL,NULL,0,0,0,10,NULL,'/',NULL,1),(44,'GERENCIA',77,78,NULL,72,300,2,7,0,0,0,0,NULL,'/',NULL,0),(45,'LOGISTICA',79,80,NULL,72,596,3,19,0,0,0,0,NULL,'/',NULL,1),(46,'REPARTO',81,84,NULL,72,659,3,10,0,0,0,1,NULL,'/',NULL,0),(48,'ALMACENAJE',85,86,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(49,'PROPIEDAD',87,88,NULL,72,1008,1,1,0,0,0,0,NULL,'/',NULL,0),(52,'CARGA AEREA',89,90,NULL,72,163,4,28,0,0,0,0,NULL,'/',NULL,0),(53,'MARKETING Y COMUNICACIÓN',91,92,NULL,72,1238,0,0,0,0,0,0,NULL,'/',NULL,1),(54,'ORNAMENTALES',93,94,NULL,72,433,3,21,0,0,0,0,NULL,'/',NULL,0),(55,'TALLER NATURAL',95,96,NULL,72,695,2,23,0,0,0,0,NULL,'/',NULL,0),(56,'TALLER ARTIFICIAL',97,98,NULL,72,1780,2,24,0,0,0,0,NULL,'/',NULL,0),(58,'CAMPOS',99,100,NULL,72,225,2,2,0,0,0,0,NULL,'/',NULL,0),(59,'MANTENIMIENTO',101,102,NULL,72,1907,4,16,0,0,0,0,NULL,'/',NULL,0),(60,'RECLAMACIONES',103,104,NULL,72,563,3,20,0,0,0,0,NULL,'/',NULL,1),(61,'VNH',105,106,NULL,73,1297,3,17,0,0,0,0,NULL,'/',NULL,0),(63,'VENTAS FRANCIA',56,57,NULL,72,277,2,27,0,0,1,0,43,'/43/',NULL,0),(66,'VERDNAMADRID',107,108,NULL,72,163,3,18,0,0,0,0,NULL,'/',NULL,0),(68,'COMPLEMENTOS',24,25,NULL,72,617,3,26,1,0,1,0,37,'/37/',NULL,0),(69,'VERDNABARNA',109,110,NULL,74,432,3,22,0,0,0,0,NULL,'/',NULL,0),(77,'PALETIZADO',82,83,NULL,72,230,4,15,1,0,1,0,46,'/46/',NULL,0),(80,'EQUIPO J VALLES',58,59,NULL,72,693,3,4,0,0,1,0,43,'/43/','jvp_equipo',1),(86,'LIMPIEZA',111,112,NULL,72,599,0,0,0,0,0,0,NULL,'/',NULL,0),(89,'COORDINACION',113,114,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(90,'TRAILER',115,116,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(91,'ARTIFICIAL',26,27,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(92,'EQUIPO SILVERIO',60,61,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','sdc_equipo',1),(93,'CONFECCION',117,118,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(94,'EQUIPO J BROCAL',62,63,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jes_equipo',1),(95,'EQUIPO C ZAMBRANO',64,65,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','czg_equipo',1),(96,'EQUIPO C LOPEZ',66,67,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','cla_equipo',1),(98,'EQUIPO RODRIGO',68,69,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','rhr_equipo',1),(101,'EQUIPO J IBAÑEZ',70,71,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jmi_equipo',1),(102,'EQ ROJO FV RUBEN C',28,29,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(103,'EQ AZUL FV A FOLQUES',30,31,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(104,'EQ AMARILLO FV NORMAN G',32,33,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(105,'EQ MORADO FV MATOU',34,35,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(106,'EQ VERDE PCA KEVIN GIMENEZ',36,37,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(107,'EQ NARANJA PCA RUBEN ZANON',38,39,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(110,'EQ ROSA PCA J BONDIA',40,41,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(111,'EQ REPONEDOR CAJAS',42,43,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(112,'CAMARA EQ EDGAR LLEO',15,16,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(113,'CAMARA EQ MARC ROCA',17,18,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(114,'EQ MARRON PCA JL NUEVO',44,45,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(115,'EQUIPO CLAUDI',72,73,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','csr_equipo',1),(120,'PCA PRODUCCION',46,47,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(121,'FV PRODUCCION',48,49,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(122,'PCA ALMACEN',50,51,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(123,'EQUIPO ELENA BASCUÑANA',74,75,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','ebt_equipo',0); +INSERT INTO `department` VALUES (1,'VERDNATURA',1,2,763,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(22,'COMPRAS',3,4,NULL,72,596,2,5,0,0,0,0,NULL,'/',NULL,1),(23,'CAMARA',14,19,NULL,72,604,2,6,1,0,1,2,37,'/37/',NULL,0),(31,'INFORMATICA',5,6,NULL,72,127,3,9,0,0,0,0,NULL,'/','informatica',1),(34,'CONTABILIDAD',7,8,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(35,'FINANZAS',9,10,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(36,'LABORAL',11,12,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,1),(37,'PRODUCCION',13,52,NULL,72,230,3,11,1,0,0,17,NULL,'/',NULL,0),(38,'SACADO',20,21,NULL,72,230,4,14,1,0,1,0,37,'/37/',NULL,0),(39,'ENCAJADO',22,23,NULL,72,230,4,12,1,0,1,0,37,'/37/',NULL,0),(41,'ADMINISTRACION',53,54,NULL,72,599,3,8,0,0,0,0,NULL,'/',NULL,1),(43,'VENTAS',55,80,NULL,0,NULL,NULL,NULL,0,0,0,12,NULL,'/',NULL,1),(44,'GERENCIA',81,82,NULL,72,300,2,7,0,0,0,0,NULL,'/',NULL,0),(45,'LOGISTICA',83,84,NULL,72,596,3,19,0,0,0,0,NULL,'/',NULL,1),(46,'REPARTO',85,88,NULL,72,659,3,10,0,0,0,1,NULL,'/',NULL,0),(48,'ALMACENAJE',89,90,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(49,'PROPIEDAD',91,92,NULL,72,1008,1,1,0,0,0,0,NULL,'/',NULL,0),(52,'CARGA AEREA',93,94,NULL,72,163,4,28,0,0,0,0,NULL,'/',NULL,0),(53,'MARKETING Y COMUNICACIÓN',95,96,NULL,72,1238,0,0,0,0,0,0,NULL,'/',NULL,1),(54,'ORNAMENTALES',97,98,NULL,72,433,3,21,0,0,0,0,NULL,'/',NULL,0),(55,'TALLER NATURAL',99,100,NULL,72,695,2,23,0,0,0,0,NULL,'/',NULL,0),(56,'TALLER ARTIFICIAL',101,102,NULL,72,1780,2,24,0,0,0,0,NULL,'/',NULL,0),(58,'CAMPOS',103,104,NULL,72,225,2,2,0,0,0,0,NULL,'/',NULL,0),(59,'MANTENIMIENTO',105,106,NULL,72,1907,4,16,0,0,0,0,NULL,'/',NULL,0),(60,'RECLAMACIONES',107,108,NULL,72,563,3,20,0,0,0,0,NULL,'/',NULL,1),(61,'VNH',109,110,NULL,73,1297,3,17,0,0,0,0,NULL,'/',NULL,0),(63,'VENTAS FRANCIA',56,57,NULL,72,277,2,27,0,0,1,0,43,'/43/',NULL,0),(66,'VERDNAMADRID',111,112,NULL,72,163,3,18,0,0,0,0,NULL,'/',NULL,0),(68,'COMPLEMENTOS',24,25,NULL,72,617,3,26,1,0,1,0,37,'/37/',NULL,0),(69,'VERDNABARNA',113,114,NULL,74,432,3,22,0,0,0,0,NULL,'/',NULL,0),(77,'PALETIZADO',86,87,NULL,72,230,4,15,1,0,1,0,46,'/46/',NULL,0),(80,'EQUIPO J VALLES',58,59,NULL,72,693,3,4,0,0,1,0,43,'/43/','jvp_equipo',1),(86,'LIMPIEZA',115,116,NULL,72,599,0,0,0,0,0,0,NULL,'/',NULL,0),(89,'COORDINACION',117,118,NULL,0,NULL,NULL,NULL,1,0,0,0,NULL,'/',NULL,0),(90,'TRAILER',119,120,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(91,'ARTIFICIAL',26,27,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(92,'EQUIPO SILVERIO',60,61,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','sdc_equipo',1),(93,'CONFECCION',121,122,NULL,0,NULL,NULL,NULL,0,0,0,0,NULL,'/',NULL,0),(94,'EQUIPO J BROCAL',62,63,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jes_equipo',1),(95,'EQUIPO C ZAMBRANO',64,65,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','czg_equipo',1),(96,'EQUIPO C LOPEZ',66,67,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','cla_equipo',1),(97,'EQUIPO D SARRION',68,69,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/',NULL,1),(98,'EQUIPO RODRIGO',70,71,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','rhr_equipo',1),(99,'EQUIPO MANOLI',72,73,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/',NULL,1),(101,'EQUIPO J IBAÑEZ',74,75,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','jmi_equipo',1),(102,'EQ ROJO FV RUBEN C',28,29,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(103,'EQ AZUL FV A FOLQUES',30,31,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(104,'EQ AMARILLO FV NORMAN G',32,33,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(105,'EQ MORADO FV MATOU',34,35,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(106,'EQ VERDE PCA KEVIN GIMENEZ',36,37,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(107,'EQ NARANJA PCA RUBEN ZANON',38,39,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(110,'EQ ROSA PCA J BONDIA',40,41,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(111,'EQ REPONEDOR CAJAS',42,43,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(112,'CAMARA EQ EDGAR LLEO',15,16,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(113,'CAMARA EQ MARC ROCA',17,18,NULL,0,NULL,NULL,NULL,1,0,2,0,23,'/37/23/',NULL,0),(114,'EQ MARRON PCA JL NUEVO',44,45,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(115,'EQUIPO CLAUDI',76,77,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','csr_equipo',1),(120,'PCA PRODUCCION',46,47,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(121,'FV PRODUCCION',48,49,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(122,'PCA ALMACEN',50,51,NULL,0,NULL,NULL,NULL,1,0,1,0,37,'/37/',NULL,0),(123,'EQUIPO ELENA BASCUÑANA',78,79,NULL,0,NULL,NULL,NULL,0,0,1,0,43,'/43/','ebt_equipo',0); /*!40000 ALTER TABLE `department` ENABLE KEYS */; UNLOCK TABLES; @@ -357,25 +364,27 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:01:02 +-- Dump completed on 2020-09-09 11:46:35 USE `cache`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: cache +-- Host: db.verdnatura.es Database: cache -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -393,25 +402,27 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:01:02 +-- Dump completed on 2020-09-09 11:46:35 USE `hedera`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: hedera +-- Host: db.verdnatura.es Database: hedera -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -449,25 +460,27 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:01:03 +-- Dump completed on 2020-09-09 11:46:36 USE `postgresql`; --- MySQL dump 10.17 Distrib 10.3.24-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.28, for osx10.15 (x86_64) -- --- Host: test-db.verdnatura.es Database: postgresql +-- Host: db.verdnatura.es Database: postgresql -- ------------------------------------------------------ --- Server version 10.4.13-MariaDB-1:10.4.13+maria~buster-log +-- Server version 5.6.25-4-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; +/*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; @@ -535,9 +548,10 @@ UNLOCK TABLES; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-12 1:01:04 +-- Dump completed on 2020-09-09 11:46:38 -- 2.40.1