First commit waiting for Budget Alert
This commit is contained in:
44
modules/axelor-open-suite/axelor-admin/build.gradle
Normal file
44
modules/axelor-open-suite/axelor-admin/build.gradle
Normal file
@ -0,0 +1,44 @@
|
||||
apply plugin: "com.axelor.app-module"
|
||||
|
||||
apply from: "../version.gradle"
|
||||
|
||||
apply {
|
||||
version = openSuiteVersion
|
||||
}
|
||||
|
||||
axelor {
|
||||
title "Axelor Admin"
|
||||
description "Axelor Admin Module"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":modules:axelor-exception")
|
||||
compile project(":modules:axelor-tool")
|
||||
}
|
||||
|
||||
def updateJsp(jspFile, check, lines) {
|
||||
def jsp = file("${rootProject.buildDir}/webapp/${jspFile}")
|
||||
def text = lines.join("\n");
|
||||
if (jsp.exists()) {
|
||||
def jspText = jsp.getText('UTF-8')
|
||||
if (jspText.indexOf(check) == -1) {
|
||||
text = jspText + text
|
||||
}
|
||||
}
|
||||
file(jsp).write(text, 'UTF-8')
|
||||
}
|
||||
|
||||
//XXX: this task should be removed when we introduce per module webapp support in axelor open platform
|
||||
task copyWebapp(type: Copy) {
|
||||
destinationDir = file(rootProject.buildDir)
|
||||
into("webapp/admin") {
|
||||
from "src/main/webapp"
|
||||
}
|
||||
doLast {
|
||||
// update index-head.jsp
|
||||
updateJsp("index-head.jsp", "diagram-js.css", [
|
||||
'<link href="admin/css/custom-width.css" rel="stylesheet">'])
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.tasks.war.dependsOn copyWebapp
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.db.repo;
|
||||
|
||||
import com.axelor.apps.base.db.ObjectDataConfigExport;
|
||||
import com.axelor.db.JpaRepository;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
|
||||
public class ObjectDataConfigExportManagementRepository extends ObjectDataConfigExportRepository {
|
||||
|
||||
@Override
|
||||
public ObjectDataConfigExport save(ObjectDataConfigExport entity) {
|
||||
|
||||
try {
|
||||
Class<? extends Model> klass =
|
||||
(Class<? extends Model>) Class.forName(entity.getModelSelect());
|
||||
|
||||
JpaRepository<? extends Model> repo = JpaRepository.of(klass);
|
||||
Object obj = repo.all().filter("self.id = ?", entity.getModelSelectId()).fetchOne();
|
||||
|
||||
if (obj != null) {
|
||||
Mapper mapper = Mapper.of(obj.getClass());
|
||||
if (mapper.getNameField() != null && mapper.getNameField().get(obj) != null) {
|
||||
entity.setRecordName(mapper.getNameField().get(obj).toString());
|
||||
} else {
|
||||
entity.setRecordName(mapper.get(obj, "id").toString());
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
|
||||
return super.save(entity);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.exceptions;
|
||||
|
||||
public interface IExceptionMessages {
|
||||
|
||||
public static final String NO_CONFIG_REQUIRED = /*$$(*/ "No configuration required" /*)*/;
|
||||
|
||||
public static final String APP_IN_USE = /*$$(*/
|
||||
"This app is used by %s. Please deactivate them before continue." /*)*/;
|
||||
|
||||
public static final String BULK_INSTALL_SUCCESS = /*$$(*/ "Apps installed successfully" /*)*/;
|
||||
|
||||
public static final String REFRESH_APP_SUCCESS = /*$$(*/ "Apps refreshed successfully" /*)*/;
|
||||
|
||||
public static final String REFRESH_APP_ERROR = /*$$(*/ "Error in refreshing app" /*)*/;
|
||||
|
||||
public static final String NO_LANGUAGE_SELECTED = /*$$(*/
|
||||
"No application language set. Please set 'application.locale' property." /*)*/;
|
||||
|
||||
public static final String DEMO_DATA_SUCCESS = /*$$(*/ "Demo data loaded successfully" /*)*/;
|
||||
|
||||
public static final String ACCESS_CONFIG_IMPORTED = /*$$(*/
|
||||
"Access config imported successfully" /*)*/;
|
||||
|
||||
public static final String OBJECT_DATA_REPLACE_MISSING = /*$$(*/ "No record found for: %s" /*)*/;
|
||||
|
||||
public static final String ROLE_IMPORT_SUCCESS = /*$$(*/ "Roles imported successfully" /*)*/;
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.job;
|
||||
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.google.inject.servlet.RequestScoper;
|
||||
import com.google.inject.servlet.ServletScopes;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Collections;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class ThreadedJob implements Job {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
if (isRunning(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = context.getJobDetail().getKey().getName();
|
||||
Thread thread = new Thread(() -> executeInThreadedRequestScope(context));
|
||||
thread.setUncaughtExceptionHandler(
|
||||
(t, e) -> {
|
||||
final Throwable cause =
|
||||
e instanceof UncheckedJobExecutionException && e.getCause() != null
|
||||
? e.getCause()
|
||||
: e;
|
||||
logger.error(cause.getMessage(), cause);
|
||||
TraceBackService.trace(cause);
|
||||
});
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
thread.start();
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
TraceBackService.trace(e);
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
float duration = (System.currentTimeMillis() - startTime) / 1000f;
|
||||
logger.info("Job {} duration: {} s", name, duration);
|
||||
JPA.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void executeInThread(JobExecutionContext context);
|
||||
|
||||
private void executeInThreadedRequestScope(JobExecutionContext context) {
|
||||
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
|
||||
try (RequestScoper.CloseableScope ignored = scope.open()) {
|
||||
executeInThread(context);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunning(JobExecutionContext context) {
|
||||
try {
|
||||
return context
|
||||
.getScheduler()
|
||||
.getCurrentlyExecutingJobs()
|
||||
.stream()
|
||||
.anyMatch(
|
||||
j ->
|
||||
j.getTrigger().equals(context.getTrigger())
|
||||
&& !j.getFireInstanceId().equals(context.getFireInstanceId()));
|
||||
} catch (SchedulerException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.job;
|
||||
|
||||
public class UncheckedJobExecutionException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -3636564667401795286L;
|
||||
|
||||
public UncheckedJobExecutionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public UncheckedJobExecutionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.module;
|
||||
|
||||
import com.axelor.app.AxelorModule;
|
||||
import com.axelor.apps.base.db.repo.ObjectDataConfigExportManagementRepository;
|
||||
import com.axelor.apps.base.db.repo.ObjectDataConfigExportRepository;
|
||||
import com.axelor.apps.base.service.ObjectDataAnonymizeService;
|
||||
import com.axelor.apps.base.service.ObjectDataAnonymizeServiceImpl;
|
||||
import com.axelor.apps.base.service.ObjectDataExportService;
|
||||
import com.axelor.apps.base.service.ObjectDataExportServiceImpl;
|
||||
import com.axelor.apps.base.service.app.AccessConfigImportService;
|
||||
import com.axelor.apps.base.service.app.AccessConfigImportServiceImpl;
|
||||
import com.axelor.apps.base.service.app.AccessTemplateService;
|
||||
import com.axelor.apps.base.service.app.AccessTemplateServiceImpl;
|
||||
import com.axelor.apps.base.service.app.AppService;
|
||||
import com.axelor.apps.base.service.app.AppServiceImpl;
|
||||
import com.axelor.apps.base.service.app.DataBackupService;
|
||||
import com.axelor.apps.base.service.app.DataBackupServiceImpl;
|
||||
|
||||
public class AdminModule extends AxelorModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(AppService.class).to(AppServiceImpl.class);
|
||||
bind(ObjectDataExportService.class).to(ObjectDataExportServiceImpl.class);
|
||||
bind(ObjectDataAnonymizeService.class).to(ObjectDataAnonymizeServiceImpl.class);
|
||||
bind(AccessTemplateService.class).to(AccessTemplateServiceImpl.class);
|
||||
bind(AccessConfigImportService.class).to(AccessConfigImportServiceImpl.class);
|
||||
bind(DataBackupService.class).to(DataBackupServiceImpl.class);
|
||||
bind(ObjectDataConfigExportRepository.class)
|
||||
.to(ObjectDataConfigExportManagementRepository.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service;
|
||||
|
||||
import com.axelor.apps.base.db.ObjectDataConfig;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface ObjectDataAnonymizeService {
|
||||
|
||||
public void anonymize(ObjectDataConfig objectDataConfig, Long recordId) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service;
|
||||
|
||||
import com.axelor.apps.base.db.DataConfigLine;
|
||||
import com.axelor.apps.base.db.ObjectDataConfig;
|
||||
import com.axelor.apps.base.db.repo.DataConfigLineRepository;
|
||||
import com.axelor.apps.base.exceptions.IExceptionMessages;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.JpaRepository;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.db.mapper.Property;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.mail.db.repo.MailMessageRepository;
|
||||
import com.axelor.meta.db.MetaField;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ObjectDataAnonymizeServiceImpl implements ObjectDataAnonymizeService {
|
||||
|
||||
@Inject private MailMessageRepository mailMessageRepo;
|
||||
|
||||
public void anonymize(ObjectDataConfig objectDataConfig, Long recordId) throws AxelorException {
|
||||
|
||||
try {
|
||||
|
||||
String rootModel = objectDataConfig.getModelSelect();
|
||||
|
||||
for (DataConfigLine line : objectDataConfig.getDataConfigLineList()) {
|
||||
String path =
|
||||
line.getTypeSelect() == DataConfigLineRepository.TYPE_PATH
|
||||
? line.getMetaFieldPath().getName()
|
||||
: line.getPath();
|
||||
|
||||
Class<? extends Model> modelClass =
|
||||
ObjectDataCommonService.findModelClass(line.getMetaModel());
|
||||
Query<? extends Model> query =
|
||||
ObjectDataCommonService.createQuery(recordId, line, modelClass);
|
||||
List<? extends Model> data = query.fetch();
|
||||
Mapper mapper = Mapper.of(modelClass);
|
||||
|
||||
int reset = line.getResetPathSelect();
|
||||
if (reset != DataConfigLineRepository.RESET_NONE
|
||||
&& line.getTypeSelect() == DataConfigLineRepository.TYPE_PATH) {
|
||||
if (reset == DataConfigLineRepository.RESET_DELETE) {
|
||||
deleteLink(mapper, path, data);
|
||||
} else {
|
||||
replaceLink(mapper, path, data, rootModel, line.getRecordSelectId());
|
||||
}
|
||||
}
|
||||
|
||||
deleteFields(line.getToDeleteMetaFieldSet(), mapper, data);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
throw new AxelorException(TraceBackRepository.TYPE_TECHNICAL, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteLink(Mapper mapper, String path, List<? extends Model> data) {
|
||||
|
||||
path = path.split("\\.")[0];
|
||||
Property property = mapper.getProperty(path);
|
||||
if (property == null || property.isRequired()) {
|
||||
return;
|
||||
}
|
||||
for (Model record : data) {
|
||||
mapper.set(record, path, null);
|
||||
JPA.save(record);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void replaceLink(
|
||||
Mapper mapper, String path, List<? extends Model> data, String rootModel, Long recordValue)
|
||||
throws ClassNotFoundException, AxelorException {
|
||||
|
||||
path = path.split("\\.")[0];
|
||||
Property property = mapper.getProperty(path);
|
||||
if (property == null) {
|
||||
return;
|
||||
}
|
||||
Class<? extends Model> klass = (Class<? extends Model>) Class.forName(rootModel);
|
||||
|
||||
JpaRepository<? extends Model> repo = JpaRepository.of(klass);
|
||||
Model object = repo.all().filter("self.id = ?1", recordValue).fetchOne();
|
||||
if (object == null) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_NO_VALUE,
|
||||
I18n.get(IExceptionMessages.OBJECT_DATA_REPLACE_MISSING),
|
||||
recordValue);
|
||||
}
|
||||
for (Model record : data) {
|
||||
mapper.set(record, path, object);
|
||||
JPA.save(record);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteFields(Set<MetaField> fields, Mapper mapper, List<? extends Model> data) {
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> defaultValues = getDefaultValues(mapper, fields);
|
||||
|
||||
for (Model record : data) {
|
||||
for (String field : defaultValues.keySet()) {
|
||||
Object object = defaultValues.get(field);
|
||||
mapper.set(record, field, object);
|
||||
}
|
||||
JPA.save(record);
|
||||
|
||||
mailMessageRepo
|
||||
.all()
|
||||
.filter(
|
||||
"self.relatedId = ?1 AND self.relatedModel = ?2",
|
||||
record.getId(),
|
||||
mapper.getBeanClass().getName())
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> getDefaultValues(Mapper mapper, Set<MetaField> fields) {
|
||||
|
||||
Map<String, Object> defaultValues = new HashMap<>();
|
||||
|
||||
LocalDate defaultDate = LocalDate.of(1900, 01, 01);
|
||||
|
||||
for (MetaField field : fields) {
|
||||
|
||||
String name = field.getName();
|
||||
Property property = mapper.getProperty(name);
|
||||
|
||||
if (!property.isRequired()) {
|
||||
defaultValues.put(name, null);
|
||||
continue;
|
||||
}
|
||||
if (property.getTarget() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (field.getTypeName()) {
|
||||
case "String":
|
||||
{
|
||||
defaultValues.put(name, "anonymous");
|
||||
break;
|
||||
}
|
||||
case "BigDecimal":
|
||||
{
|
||||
defaultValues.put(name, BigDecimal.ZERO);
|
||||
break;
|
||||
}
|
||||
case "Integer":
|
||||
{
|
||||
defaultValues.put(name, new Integer(0));
|
||||
break;
|
||||
}
|
||||
case "Boolean":
|
||||
{
|
||||
defaultValues.put(name, Boolean.FALSE);
|
||||
break;
|
||||
}
|
||||
case "Long":
|
||||
{
|
||||
defaultValues.put(name, new Long(0));
|
||||
break;
|
||||
}
|
||||
case "LocalTime":
|
||||
{
|
||||
defaultValues.put(name, LocalTime.MIDNIGHT);
|
||||
break;
|
||||
}
|
||||
case "LocalDateTime":
|
||||
{
|
||||
defaultValues.put(name, defaultDate.atStartOfDay());
|
||||
break;
|
||||
}
|
||||
case "ZonedDateTime":
|
||||
{
|
||||
defaultValues.put(name, defaultDate.atStartOfDay(ZoneId.systemDefault()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValues;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service;
|
||||
|
||||
import com.axelor.apps.base.db.DataConfigLine;
|
||||
import com.axelor.apps.base.db.repo.DataConfigLineRepository;
|
||||
import com.axelor.db.JpaRepository;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.MetaField;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class ObjectDataCommonService {
|
||||
|
||||
public static ResourceBundle getResourceBundle(String language) {
|
||||
|
||||
ResourceBundle bundle;
|
||||
|
||||
if (language == null) {
|
||||
bundle = I18n.getBundle();
|
||||
} else if (language.equals("fr")) {
|
||||
bundle = I18n.getBundle(Locale.FRANCE);
|
||||
} else {
|
||||
bundle = I18n.getBundle(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static String getNameColumn(MetaField field) throws ClassNotFoundException {
|
||||
|
||||
MetaModel metaModel = Beans.get(MetaModelRepository.class).findByName(field.getTypeName());
|
||||
|
||||
return Mapper.of(findModelClass(metaModel)).getNameField().getName();
|
||||
}
|
||||
|
||||
public static Class<? extends Model> findModelClass(MetaModel metaModel)
|
||||
throws ClassNotFoundException {
|
||||
|
||||
Class<?> modelClass = Class.forName(metaModel.getFullName());
|
||||
|
||||
return (Class<? extends Model>) modelClass;
|
||||
}
|
||||
|
||||
public static Query<? extends Model> createQuery(
|
||||
Long recordId, DataConfigLine line, Class<? extends Model> modelClass) {
|
||||
|
||||
Query<? extends Model> query = JpaRepository.of(modelClass).all();
|
||||
String filter =
|
||||
line.getTypeSelect() == DataConfigLineRepository.TYPE_PATH
|
||||
? createFilter(line.getMetaFieldPath().getName())
|
||||
: line.getPath();
|
||||
query.filter(filter, recordId);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static String createFilter(String path) {
|
||||
|
||||
if (path == null || path.equals("id")) {
|
||||
path = "id";
|
||||
} else if (!path.endsWith(".id")) {
|
||||
path = path + ".id";
|
||||
}
|
||||
|
||||
return "self." + path + " = ?1";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service;
|
||||
|
||||
import com.axelor.apps.base.db.ObjectDataConfig;
|
||||
import com.axelor.apps.base.db.ObjectDataConfigExport;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
|
||||
public interface ObjectDataExportService {
|
||||
|
||||
public MetaFile export(
|
||||
ObjectDataConfig objectDataConfig, ObjectDataConfigExport objDataConfigExport)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service;
|
||||
|
||||
import com.axelor.apps.base.db.DataConfigLine;
|
||||
import com.axelor.apps.base.db.ObjectDataConfig;
|
||||
import com.axelor.apps.base.db.ObjectDataConfigExport;
|
||||
import com.axelor.common.Inflector;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.db.mapper.Property;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.MetaStore;
|
||||
import com.axelor.meta.db.MetaField;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.schema.views.Selection.Option;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import com.opencsv.CSVWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ObjectDataExportServiceImpl implements ObjectDataExportService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ObjectDataExportServiceImpl.class);
|
||||
|
||||
private Inflector inflector = Inflector.getInstance();
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
@Override
|
||||
public MetaFile export(
|
||||
ObjectDataConfig objectDataConfig, ObjectDataConfigExport objDataConfigExport)
|
||||
throws AxelorException {
|
||||
|
||||
Long recordId = objDataConfigExport.getModelSelectId();
|
||||
String language = objDataConfigExport.getLangSelect();
|
||||
String format = objDataConfigExport.getExportFormatSelect();
|
||||
|
||||
try {
|
||||
logger.debug(
|
||||
"Exporting data for model: {}, id: {}, language: {}, format: {}",
|
||||
objectDataConfig.getModelSelect(),
|
||||
recordId,
|
||||
language,
|
||||
format);
|
||||
|
||||
Map<String, List<String[]>> data = createData(objectDataConfig, recordId, language);
|
||||
|
||||
if (format != null && format.equals("csv")) {
|
||||
return writeCSV(data);
|
||||
} else {
|
||||
return writeExcel(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
throw new AxelorException(TraceBackRepository.TYPE_TECHNICAL, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String[]>> createData(
|
||||
ObjectDataConfig objectDataConfig, Long recordId, String language)
|
||||
throws ClassNotFoundException {
|
||||
|
||||
Map<String, List<String[]>> data = new HashMap<>();
|
||||
|
||||
for (DataConfigLine line : objectDataConfig.getDataConfigLineList()) {
|
||||
|
||||
MetaModel metaModel = line.getMetaModel();
|
||||
|
||||
logger.debug("Create data for: {}", metaModel.getName());
|
||||
|
||||
Class<? extends Model> modelClass = ObjectDataCommonService.findModelClass(metaModel);
|
||||
|
||||
Query<? extends Model> query =
|
||||
ObjectDataCommonService.createQuery(recordId, line, modelClass);
|
||||
|
||||
ResourceBundle bundle = ObjectDataCommonService.getResourceBundle(language);
|
||||
|
||||
String[][] fieldsData = createFieldsData(line.getToExportMetaFieldSet(), bundle);
|
||||
|
||||
Map<String, String> selectMap = getSelectMap(Mapper.of(modelClass));
|
||||
|
||||
List<String[]> dataList = fetchData(fieldsData, query, selectMap, bundle);
|
||||
|
||||
data.put(line.getTabName(), dataList);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private Map<String, String> getSelectMap(Mapper mapper) {
|
||||
|
||||
Map<String, String> selectionMap = new HashMap<>();
|
||||
|
||||
for (Property property : mapper.getProperties()) {
|
||||
String selection = property.getSelection();
|
||||
if (!Strings.isNullOrEmpty(selection)) {
|
||||
selectionMap.put(property.getName(), selection);
|
||||
}
|
||||
}
|
||||
|
||||
return selectionMap;
|
||||
}
|
||||
|
||||
private String[][] createFieldsData(Set<MetaField> metaFields, ResourceBundle bundle)
|
||||
throws ClassNotFoundException {
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> labels = new ArrayList<>();
|
||||
|
||||
for (MetaField field : metaFields) {
|
||||
String label = field.getLabel();
|
||||
String name = field.getName();
|
||||
|
||||
if (Strings.isNullOrEmpty(label)) {
|
||||
label = inflector.humanize(name);
|
||||
}
|
||||
|
||||
if (field.getRelationship() != null) {
|
||||
name = name + "." + ObjectDataCommonService.getNameColumn(field);
|
||||
}
|
||||
|
||||
names.add(name);
|
||||
|
||||
String translated = bundle.getString(label);
|
||||
|
||||
if (!Strings.isNullOrEmpty(translated)) {
|
||||
labels.add(translated);
|
||||
} else {
|
||||
labels.add(label);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Fields: {}", names);
|
||||
logger.debug("Labels: {}", labels);
|
||||
|
||||
return new String[][] {
|
||||
names.toArray(new String[names.size()]), labels.toArray(new String[labels.size()])
|
||||
};
|
||||
}
|
||||
|
||||
private List<String[]> fetchData(
|
||||
String[][] fieldsData,
|
||||
Query<? extends Model> query,
|
||||
Map<String, String> selectMap,
|
||||
ResourceBundle bundle) {
|
||||
|
||||
List<String[]> dataList = new ArrayList<>();
|
||||
dataList.add(fieldsData[1]);
|
||||
|
||||
List<Map> records = query.select(fieldsData[0]).fetch(0, 0);
|
||||
|
||||
for (Map<String, Object> record : records) {
|
||||
|
||||
List<String> datas = new ArrayList<>();
|
||||
|
||||
for (String field : fieldsData[0]) {
|
||||
Object object = record.get(field);
|
||||
if (object == null) {
|
||||
datas.add("");
|
||||
continue;
|
||||
}
|
||||
if (selectMap.containsKey(field)) {
|
||||
String selection = selectMap.get(field);
|
||||
Option option = MetaStore.getSelectionItem(selection, object.toString());
|
||||
if (option == null) {
|
||||
datas.add("");
|
||||
continue;
|
||||
}
|
||||
String optionTitle = option.getTitle();
|
||||
optionTitle = bundle.getString(optionTitle);
|
||||
if (optionTitle != null) {
|
||||
datas.add(optionTitle);
|
||||
} else {
|
||||
datas.add(option.getTitle());
|
||||
}
|
||||
} else {
|
||||
datas.add(object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
dataList.add(datas.toArray(new String[fieldsData[0].length]));
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
private MetaFile writeCSV(Map<String, List<String[]>> data) throws IOException {
|
||||
|
||||
File zipFile = MetaFiles.createTempFile("Data", ".zip").toFile();
|
||||
ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zipFile));
|
||||
|
||||
for (String model : data.keySet()) {
|
||||
File modelFile = MetaFiles.createTempFile(model, ".csv").toFile();
|
||||
CSVWriter writer = new CSVWriter(new FileWriter(modelFile), ';');
|
||||
writer.writeAll(data.get(model));
|
||||
writer.close();
|
||||
zout.putNextEntry(new ZipEntry(model + ".csv"));
|
||||
zout.write(IOUtils.toByteArray(new FileInputStream(modelFile)));
|
||||
zout.closeEntry();
|
||||
}
|
||||
zout.close();
|
||||
|
||||
return metaFiles.upload(zipFile);
|
||||
}
|
||||
|
||||
private MetaFile writeExcel(Map<String, List<String[]>> data) throws IOException {
|
||||
|
||||
XSSFWorkbook workBook = new XSSFWorkbook();
|
||||
|
||||
for (String model : data.keySet()) {
|
||||
XSSFSheet sheet = workBook.createSheet(model);
|
||||
int count = 0;
|
||||
for (String[] record : data.get(model)) {
|
||||
XSSFRow row = sheet.createRow(count);
|
||||
int cellCount = 0;
|
||||
for (String val : record) {
|
||||
XSSFCell cell = row.createCell(cellCount);
|
||||
cell.setCellValue(val);
|
||||
cellCount++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
File excelFile = MetaFiles.createTempFile("Data", ".xls").toFile();
|
||||
FileOutputStream out = new FileOutputStream(excelFile);
|
||||
workBook.write(out);
|
||||
out.close();
|
||||
|
||||
return metaFiles.upload(excelFile);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
|
||||
public interface AccessConfigImportService {
|
||||
|
||||
public void importAccessConfig(MetaFile metaFile) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.AccessConfig;
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.apps.base.db.repo.AccessConfigRepository;
|
||||
import com.axelor.auth.db.Permission;
|
||||
import com.axelor.auth.db.Role;
|
||||
import com.axelor.auth.db.repo.PermissionRepository;
|
||||
import com.axelor.auth.db.repo.RoleRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaMenu;
|
||||
import com.axelor.meta.db.repo.MetaMenuRepository;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
public class AccessConfigImportServiceImpl implements AccessConfigImportService {
|
||||
|
||||
@Inject private AppService appService;
|
||||
|
||||
@Inject private AccessConfigRepository accessConfigRepo;
|
||||
|
||||
@Inject private PermissionRepository permissionRepo;
|
||||
|
||||
@Inject private RoleRepository roleRepo;
|
||||
|
||||
@Inject private MetaMenuRepository metaMenuRepo;
|
||||
|
||||
@Override
|
||||
public void importAccessConfig(MetaFile metaFile) throws AxelorException {
|
||||
|
||||
if (metaFile == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OPCPackage pkg = OPCPackage.open(MetaFiles.getPath(metaFile).toString());
|
||||
XSSFWorkbook workBook = new XSSFWorkbook(pkg);
|
||||
processWorkbook(workBook);
|
||||
} catch (InvalidFormatException | IOException e) {
|
||||
TraceBackService.trace(e);
|
||||
throw new AxelorException(TraceBackRepository.TYPE_TECHNICAL, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processWorkbook(XSSFWorkbook workBook) {
|
||||
|
||||
Iterator<XSSFSheet> sheetIter = workBook.iterator();
|
||||
|
||||
while (sheetIter.hasNext()) {
|
||||
XSSFSheet sheet = sheetIter.next();
|
||||
String name = sheet.getSheetName();
|
||||
if (name.endsWith("-menu")) {
|
||||
importMenuAccess(sheet);
|
||||
} else {
|
||||
importObjectAccess(sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void importObjectAccess(XSSFSheet sheet) {
|
||||
|
||||
App app = appService.getApp(sheet.getSheetName());
|
||||
if (app == null) {
|
||||
return;
|
||||
}
|
||||
Iterator<Row> rowIter = sheet.iterator();
|
||||
Map<Integer, AccessConfig> accessMap = null;
|
||||
while (rowIter.hasNext()) {
|
||||
if (accessMap == null) {
|
||||
accessMap = getAccessConfig(rowIter.next(), app);
|
||||
continue;
|
||||
}
|
||||
createObjectRoles(accessMap, rowIter.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Map<Integer, AccessConfig> getAccessConfig(Row row, App app) {
|
||||
|
||||
Map<Integer, AccessConfig> configMap = new HashMap<>();
|
||||
|
||||
Iterator<Cell> cellIter = row.iterator();
|
||||
cellIter.next();
|
||||
while (cellIter.hasNext()) {
|
||||
Cell cell = cellIter.next();
|
||||
String name = cell.getStringCellValue();
|
||||
if (name != null) {
|
||||
AccessConfig config =
|
||||
accessConfigRepo.all().filter("self.name = ?1 and self.app = ?2", name, app).fetchOne();
|
||||
if (config == null) {
|
||||
config = new AccessConfig(name);
|
||||
config.setApp(app);
|
||||
config = accessConfigRepo.save(config);
|
||||
}
|
||||
configMap.put(cell.getColumnIndex(), config);
|
||||
}
|
||||
}
|
||||
|
||||
return configMap;
|
||||
}
|
||||
|
||||
private void createObjectRoles(Map<Integer, AccessConfig> accessMap, Row row) {
|
||||
|
||||
Iterator<Cell> cellIter = row.iterator();
|
||||
String obj = cellIter.next().getStringCellValue();
|
||||
while (cellIter.hasNext()) {
|
||||
Cell cell = cellIter.next();
|
||||
String value = cell.getStringCellValue();
|
||||
if (cell == null || Strings.isNullOrEmpty(value) || invalidValue(value)) {
|
||||
continue;
|
||||
}
|
||||
AccessConfig config = accessMap.get(cell.getColumnIndex());
|
||||
Permission permission = getPermission(obj, value.trim(), config);
|
||||
addRole(config, permission);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean invalidValue(String value) {
|
||||
|
||||
return !"rwcde".startsWith(value);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Permission getPermission(String model, String value, AccessConfig config) {
|
||||
|
||||
String[] objs = model.split("\\.");
|
||||
String obj = objs[objs.length - 1];
|
||||
String pkg = objs[objs.length - 3];
|
||||
String name = "perm." + pkg + "." + obj + "." + value;
|
||||
Permission permission =
|
||||
permissionRepo.all().filter("self.name = ?1 and self.object = ?2", name, model).fetchOne();
|
||||
if (permission == null) {
|
||||
permission = new Permission(name);
|
||||
permission.setObject(model);
|
||||
}
|
||||
boolean defaultRight = value.equals("all");
|
||||
permission.setCanCreate(defaultRight);
|
||||
permission.setCanRead(defaultRight);
|
||||
permission.setCanWrite(defaultRight);
|
||||
permission.setCanRemove(defaultRight);
|
||||
permission.setCanExport(defaultRight);
|
||||
|
||||
if (!defaultRight) {
|
||||
for (char c : value.toCharArray()) {
|
||||
switch (c) {
|
||||
case 'r':
|
||||
{
|
||||
permission.setCanRead(true);
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
{
|
||||
permission.setCanCreate(true);
|
||||
break;
|
||||
}
|
||||
case 'w':
|
||||
{
|
||||
permission.setCanWrite(true);
|
||||
break;
|
||||
}
|
||||
case 'd':
|
||||
{
|
||||
permission.setCanRemove(true);
|
||||
break;
|
||||
}
|
||||
case 'e':
|
||||
{
|
||||
permission.setCanExport(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return permissionRepo.save(permission);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void addRole(AccessConfig config, Permission permission) {
|
||||
|
||||
String name = config.getApp().getCode() + "." + config.getName();
|
||||
Role role = roleRepo.findByName(name);
|
||||
if (role == null) {
|
||||
role = new Role(name);
|
||||
}
|
||||
role.addPermission(permission);
|
||||
role = roleRepo.save(role);
|
||||
config.addRoleSetItem(role);
|
||||
accessConfigRepo.save(config);
|
||||
}
|
||||
|
||||
private void importMenuAccess(XSSFSheet sheet) {
|
||||
|
||||
App app = appService.getApp(sheet.getSheetName().split("-")[0]);
|
||||
if (app == null) {
|
||||
return;
|
||||
}
|
||||
Iterator<Row> rowIter = sheet.iterator();
|
||||
Map<Integer, AccessConfig> accessMap = null;
|
||||
while (rowIter.hasNext()) {
|
||||
if (accessMap == null) {
|
||||
accessMap = getAccessConfig(rowIter.next(), app);
|
||||
continue;
|
||||
}
|
||||
createMenuRoles(accessMap, rowIter.next());
|
||||
}
|
||||
}
|
||||
|
||||
private void createMenuRoles(Map<Integer, AccessConfig> accessMap, Row row) {
|
||||
|
||||
Iterator<Cell> cellIter = row.iterator();
|
||||
String menu = cellIter.next().getStringCellValue().trim();
|
||||
while (cellIter.hasNext()) {
|
||||
Cell cell = cellIter.next();
|
||||
String value = cell.getStringCellValue();
|
||||
if (cell == null || Strings.isNullOrEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
AccessConfig config = accessMap.get(cell.getColumnIndex());
|
||||
addRole(config, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void addRole(AccessConfig config, String menu) {
|
||||
|
||||
String name = config.getApp().getCode() + "." + config.getName();
|
||||
Role role = roleRepo.findByName(name);
|
||||
if (role == null) {
|
||||
role = new Role(name);
|
||||
}
|
||||
MetaMenu metaMenu = metaMenuRepo.findByName(menu);
|
||||
if (metaMenu != null) {
|
||||
metaMenu.addRole(role);
|
||||
metaMenuRepo.save(metaMenu);
|
||||
}
|
||||
config.addRoleSetItem(role);
|
||||
accessConfigRepo.save(config);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
|
||||
public interface AccessTemplateService {
|
||||
|
||||
public MetaFile generateTemplate() throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaAction;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaJsonRecord;
|
||||
import com.axelor.meta.db.MetaMenu;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaMenuRepository;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
public class AccessTemplateServiceImpl implements AccessTemplateService {
|
||||
|
||||
private Map<String, String> menuApp;
|
||||
|
||||
private Map<String, String> objMenu;
|
||||
|
||||
private List<String> configMenus;
|
||||
|
||||
private List<String> appMenus;
|
||||
|
||||
private static final String[] objectHeaders = new String[] {"Object", "User", "Manager"};
|
||||
private static final String[] menuHeaders = new String[] {"Menu", "User", "Manager"};
|
||||
|
||||
private String defaultApp = null;
|
||||
|
||||
@Inject private MetaMenuRepository metaMenuRepo;
|
||||
|
||||
@Inject private AppService appService;
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
@Inject private MetaModelRepository metaModelRepo;
|
||||
|
||||
@Override
|
||||
public MetaFile generateTemplate() throws AxelorException {
|
||||
|
||||
try {
|
||||
|
||||
menuApp = new HashMap<>();
|
||||
objMenu = new HashMap<>();
|
||||
configMenus = new ArrayList<>();
|
||||
appMenus = new ArrayList<>();
|
||||
defaultApp = null;
|
||||
|
||||
App app = appService.getApp("base");
|
||||
if (app == null) {
|
||||
return null;
|
||||
}
|
||||
defaultApp = app.getCode();
|
||||
|
||||
getMenusPerApp();
|
||||
|
||||
updateNoMenuObjects();
|
||||
|
||||
return createExcel();
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
throw new AxelorException(TraceBackRepository.TYPE_TECHNICAL, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void getMenusPerApp() {
|
||||
|
||||
List<MetaMenu> menus = metaMenuRepo.all().filter("self.parent is null").order("id").fetch();
|
||||
|
||||
processMenu(menus.iterator());
|
||||
}
|
||||
|
||||
private void processMenu(Iterator<MetaMenu> menuIter) {
|
||||
|
||||
if (!menuIter.hasNext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MetaMenu menu = menuIter.next();
|
||||
|
||||
String app = getApp(menu);
|
||||
menuApp.put(menu.getName(), app);
|
||||
MetaAction action = menu.getAction();
|
||||
if (action != null && action.getType().equals("action-view")) {
|
||||
String model = action.getModel();
|
||||
if (validModel(model)) {
|
||||
String menuName = getObjMenu(menu);
|
||||
if (menuName != null) {
|
||||
objMenu.put(model, menuName);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<MetaMenu> menus = metaMenuRepo.all().filter("self.parent = ?1", menu).order("id").fetch();
|
||||
processMenu(menus.iterator());
|
||||
processMenu(menuIter);
|
||||
}
|
||||
|
||||
private boolean validModel(String model) {
|
||||
|
||||
if (model == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objMenu.containsKey(model)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.equals(MetaJsonRecord.class.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getObjMenu(MetaMenu menu) {
|
||||
|
||||
if (appMenus.contains(menu.getName())) {
|
||||
return menu.getName();
|
||||
}
|
||||
if (configMenus.contains(menu.getName())) {
|
||||
return menu.getName();
|
||||
}
|
||||
|
||||
MetaMenu parent = menu.getParent();
|
||||
while (parent != null) {
|
||||
if (configMenus.contains(parent.getName())) {
|
||||
break;
|
||||
}
|
||||
if (appMenus.contains(parent.getName())) {
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
return parent.getName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getApp(MetaMenu menu) {
|
||||
|
||||
String appCode = null;
|
||||
String condition = menu.getConditionToCheck();
|
||||
|
||||
if (condition != null) {
|
||||
String[] cond = condition.split("__config__\\.app\\.isApp\\('");
|
||||
if (cond.length > 1) {
|
||||
App app = appService.getApp(cond[1].split("'")[0]);
|
||||
if (app != null) {
|
||||
if (condition.trim().equals("__config__.app.isApp('" + app.getCode() + "')")
|
||||
&& menu.getAction() == null) {
|
||||
appMenus.add(menu.getName());
|
||||
}
|
||||
appCode = app.getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetaMenu parent = menu.getParent();
|
||||
if (appCode == null && parent != null && menuApp.containsKey(parent.getName())) {
|
||||
appCode = menuApp.get(parent.getName());
|
||||
}
|
||||
|
||||
if (parent == null && appCode == null) {
|
||||
configMenus.add(menu.getName());
|
||||
}
|
||||
|
||||
if (menu.getTitle().equals("Configuration") || menu.getTitle().equals("Configurations")) {
|
||||
configMenus.add(menu.getName());
|
||||
appMenus.remove(menu.getName());
|
||||
}
|
||||
|
||||
if (appCode == null) {
|
||||
return defaultApp;
|
||||
}
|
||||
|
||||
return appCode;
|
||||
}
|
||||
|
||||
private MetaFile createExcel() throws FileNotFoundException, IOException {
|
||||
|
||||
XSSFWorkbook workBook = new XSSFWorkbook();
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.addAll(objMenu.keySet());
|
||||
Collections.sort(keys);
|
||||
|
||||
Set<String> menuProcessed = new HashSet<>();
|
||||
for (String obj : keys) {
|
||||
String menu = objMenu.get(obj);
|
||||
String app = menuApp.get(menu);
|
||||
if (app == null) {
|
||||
continue;
|
||||
}
|
||||
writeObjectSheet(workBook, obj, menu, app);
|
||||
if (!menuProcessed.contains(menu)) {
|
||||
writeMenuSheet(workBook, menu, app);
|
||||
menuProcessed.add(menu);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> menusRemaining = new ArrayList<>();
|
||||
menusRemaining.addAll(appMenus);
|
||||
for (String menu : menusRemaining) {
|
||||
writeMenuSheet(workBook, menu, menuApp.get(menu));
|
||||
}
|
||||
|
||||
return createMetaFile(workBook);
|
||||
}
|
||||
|
||||
public MetaFile createMetaFile(XSSFWorkbook workBook) throws IOException, FileNotFoundException {
|
||||
|
||||
Path path = MetaFiles.createTempFile("AccessConfigTemplate", ".xlsx");
|
||||
|
||||
File file = path.toFile();
|
||||
FileOutputStream fout = new FileOutputStream(file);
|
||||
workBook.write(fout);
|
||||
fout.close();
|
||||
|
||||
return metaFiles.upload(file);
|
||||
}
|
||||
|
||||
public void writeObjectSheet(XSSFWorkbook workBook, String obj, String menu, String app) {
|
||||
|
||||
XSSFSheet sheet = workBook.getSheet(app);
|
||||
if (sheet == null) {
|
||||
sheet = workBook.createSheet(app);
|
||||
writeRow(sheet, objectHeaders);
|
||||
}
|
||||
String usersRights = appMenus.contains(menu) ? "rwcde" : "r";
|
||||
writeRow(sheet, new String[] {obj, usersRights, "rwcde"});
|
||||
}
|
||||
|
||||
private void writeMenuSheet(XSSFWorkbook workBook, String menu, String app) {
|
||||
XSSFSheet sheet = workBook.getSheet(app + "-menu");
|
||||
if (sheet == null) {
|
||||
sheet = workBook.createSheet(app + "-menu");
|
||||
writeRow(sheet, menuHeaders);
|
||||
}
|
||||
String usersRights = configMenus.contains(menu) ? "" : "x";
|
||||
writeRow(sheet, new String[] {menu, usersRights, "x"});
|
||||
appMenus.remove(menu);
|
||||
}
|
||||
|
||||
private void writeRow(XSSFSheet sheet, String[] values) {
|
||||
|
||||
XSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows());
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
XSSFCell cell = row.createCell(i);
|
||||
cell.setCellValue(values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNoMenuObjects() {
|
||||
|
||||
List<MetaModel> metaModels =
|
||||
metaModelRepo.all().filter("self.fullName not in ?1", objMenu.keySet()).fetch();
|
||||
|
||||
Iterator<MetaModel> modelIter = metaModels.iterator();
|
||||
String appMenu = objMenu.get(App.class.getName());
|
||||
while (modelIter.hasNext()) {
|
||||
MetaModel model = modelIter.next();
|
||||
try {
|
||||
Class klass = Class.forName(model.getFullName());
|
||||
if (App.class.isAssignableFrom(klass)) {
|
||||
objMenu.put(model.getFullName(), appMenu);
|
||||
modelIter.remove();
|
||||
} else if (addObject(model)) {
|
||||
modelIter.remove();
|
||||
} else {
|
||||
objMenu.put(model.getFullName(), appMenu);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addObject(MetaModel model) {
|
||||
|
||||
for (String key : objMenu.keySet()) {
|
||||
if (model.getFullName().contains(key)) {
|
||||
objMenu.put(model.getFullName(), objMenu.get(key));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String pkgName = model.getPackageName();
|
||||
for (String key : objMenu.keySet()) {
|
||||
String objPkg = key.substring(0, key.lastIndexOf("."));
|
||||
if (pkgName.equals(objPkg)) {
|
||||
objMenu.put(model.getFullName(), objMenu.get(key));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface AppService {
|
||||
|
||||
public App importDataDemo(App app) throws AxelorException;
|
||||
|
||||
public App getApp(String type);
|
||||
|
||||
public boolean isApp(String type);
|
||||
|
||||
public App installApp(App app, String language) throws AxelorException;
|
||||
|
||||
public App unInstallApp(App app) throws AxelorException;
|
||||
|
||||
public void refreshApp() throws IOException, ClassNotFoundException;
|
||||
|
||||
public void bulkInstall(Collection<App> apps, Boolean importDeomo, String language)
|
||||
throws AxelorException;
|
||||
|
||||
public App importRoles(App app) throws AxelorException;
|
||||
|
||||
public void importRoles() throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,568 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.app.AppSettings;
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.apps.base.db.repo.AppRepository;
|
||||
import com.axelor.apps.base.exceptions.IExceptionMessages;
|
||||
import com.axelor.common.FileUtils;
|
||||
import com.axelor.common.Inflector;
|
||||
import com.axelor.data.Importer;
|
||||
import com.axelor.data.csv.CSVConfig;
|
||||
import com.axelor.data.csv.CSVImporter;
|
||||
import com.axelor.data.csv.CSVInput;
|
||||
import com.axelor.data.xml.XMLImporter;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.meta.MetaScanner;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.persistence.Query;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class AppServiceImpl implements AppService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AppService.class);
|
||||
|
||||
private static final String DIR_DEMO = "demo";
|
||||
|
||||
private static final String DIR_INIT = "data-init" + File.separator + "app";
|
||||
|
||||
private static final String DIR_ROLES = "roles";
|
||||
|
||||
private static final String CONFIG_PATTERN = "-config.xml";
|
||||
|
||||
private static final String IMG_DIR = "img";
|
||||
|
||||
private static final String EXT_DIR = "extra";
|
||||
|
||||
private static Pattern patCsv = Pattern.compile("^\\<\\s*csv-inputs");
|
||||
|
||||
private static Pattern patXml = Pattern.compile("^\\<\\s*xml-inputs");
|
||||
|
||||
private Inflector inflector = Inflector.getInstance();
|
||||
|
||||
@Inject private AppRepository appRepo;
|
||||
|
||||
@Inject private MetaModelRepository metaModelRepo;
|
||||
|
||||
@Override
|
||||
public App importDataDemo(App app) throws AxelorException {
|
||||
|
||||
if (app.getDemoDataLoaded()) {
|
||||
return app;
|
||||
}
|
||||
|
||||
log.debug("Demo import: App code: {}, App lang: {}", app.getCode(), app.getLanguageSelect());
|
||||
importParentData(app);
|
||||
|
||||
String lang = getLanguage(app);
|
||||
if (lang == null) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessages.NO_LANGUAGE_SELECTED));
|
||||
}
|
||||
|
||||
importData(app, DIR_DEMO, true);
|
||||
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
app.setDemoDataLoaded(true);
|
||||
|
||||
return saveApp(app);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public App saveApp(App app) {
|
||||
return appRepo.save(app);
|
||||
}
|
||||
|
||||
private void importData(App app, String dataDir, boolean useLang) {
|
||||
|
||||
String modules = app.getModules();
|
||||
if (modules == null) {
|
||||
return;
|
||||
}
|
||||
String code = app.getCode();
|
||||
String lang = useLang ? getLanguage(app) : "";
|
||||
|
||||
log.debug("Data import: DataDir: {}, App code: {}, App lang: {}", dataDir, code, lang);
|
||||
|
||||
for (String module : modules.split(",")) {
|
||||
File tmp = extract(module, dataDir, lang, code);
|
||||
if (tmp == null) {
|
||||
continue;
|
||||
}
|
||||
log.debug("Importing from module: {}", module);
|
||||
importPerConfig(code, new File(tmp, dataDir));
|
||||
}
|
||||
}
|
||||
|
||||
private void importPerConfig(String appCode, File dataDir) {
|
||||
|
||||
try {
|
||||
File[] configs =
|
||||
dataDir.listFiles(
|
||||
new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith(appCode + "-") && name.endsWith(CONFIG_PATTERN);
|
||||
}
|
||||
});
|
||||
|
||||
if (configs.length == 0) {
|
||||
log.debug("No config file found for the app: {}", appCode);
|
||||
return;
|
||||
}
|
||||
|
||||
Arrays.sort(configs);
|
||||
|
||||
for (File config : configs) {
|
||||
runImport(config, dataDir);
|
||||
}
|
||||
|
||||
} finally {
|
||||
clean(dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
private String getLanguage(App app) {
|
||||
|
||||
String lang = app.getLanguageSelect();
|
||||
|
||||
if (app.getLanguageSelect() == null) {
|
||||
lang = AppSettings.get().get("application.locale");
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
private void importParentData(App app) throws AxelorException {
|
||||
|
||||
List<App> depends = getDepends(app, true);
|
||||
|
||||
for (App parent : depends) {
|
||||
parent = appRepo.find(parent.getId());
|
||||
if (!parent.getDemoDataLoaded()) {
|
||||
importDataDemo(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private App importDataInit(App app) {
|
||||
|
||||
String lang = getLanguage(app);
|
||||
if (lang == null) {
|
||||
return app;
|
||||
}
|
||||
|
||||
importData(app, DIR_INIT, true);
|
||||
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
app.setInitDataLoaded(true);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private void runImport(File config, File data) {
|
||||
|
||||
log.debug(
|
||||
"Running import with config path: {}, data path: {}",
|
||||
config.getAbsolutePath(),
|
||||
data.getAbsolutePath());
|
||||
|
||||
try {
|
||||
Scanner scanner = new Scanner(config);
|
||||
Importer importer = null;
|
||||
while (scanner.hasNextLine()) {
|
||||
String str = scanner.nextLine();
|
||||
if (patCsv.matcher(str).find()) {
|
||||
importer = new CSVImporter(config.getAbsolutePath(), data.getAbsolutePath(), null);
|
||||
break;
|
||||
}
|
||||
if (patXml.matcher(str).find()) {
|
||||
importer = new XMLImporter(config.getAbsolutePath(), data.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
|
||||
if (importer != null) {
|
||||
importer.run();
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private File extract(String module, String dirName, String lang, String code) {
|
||||
String dirNamePattern = dirName.replaceAll("/|\\\\", "(/|\\\\\\\\)");
|
||||
List<URL> files = new ArrayList<URL>();
|
||||
files.addAll(MetaScanner.findAll(module, dirNamePattern, code + "(-+.*)?" + CONFIG_PATTERN));
|
||||
if (files.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (lang.isEmpty()) {
|
||||
files.addAll(MetaScanner.findAll(module, dirNamePattern, code + "*"));
|
||||
} else {
|
||||
String dirPath = dirName + "/";
|
||||
files.addAll(fetchUrls(module, dirPath + IMG_DIR));
|
||||
files.addAll(fetchUrls(module, dirPath + EXT_DIR));
|
||||
files.addAll(fetchUrls(module, dirPath + lang));
|
||||
}
|
||||
|
||||
final File tmp = Files.createTempDir();
|
||||
final String dir = dirName.replace("\\", "/");
|
||||
|
||||
for (URL file : files) {
|
||||
String name = file.toString();
|
||||
name = name.substring(name.lastIndexOf(dir));
|
||||
if (!lang.isEmpty()) {
|
||||
name = name.replace(dir + "/" + lang, dir);
|
||||
}
|
||||
if (File.separatorChar == '\\') {
|
||||
name = name.replace("/", "\\");
|
||||
}
|
||||
try {
|
||||
copy(file.openStream(), tmp, name);
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private List<URL> fetchUrls(String module, String fileName) {
|
||||
final String fileNamePattern = fileName.replaceAll("/|\\\\", "(/|\\\\\\\\)");
|
||||
return MetaScanner.findAll(module, fileNamePattern, "(.+?)");
|
||||
}
|
||||
|
||||
private void copy(InputStream in, File toDir, String name) throws IOException {
|
||||
File dst = FileUtils.getFile(toDir, name);
|
||||
Files.createParentDirs(dst);
|
||||
FileOutputStream out = new FileOutputStream(dst);
|
||||
try {
|
||||
ByteStreams.copy(in, out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void clean(File file) {
|
||||
if (file.isDirectory()) {
|
||||
for (File child : file.listFiles()) {
|
||||
clean(child);
|
||||
}
|
||||
file.delete();
|
||||
} else if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public App getApp(String code) {
|
||||
return appRepo.findByCode(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApp(String code) {
|
||||
App app = getApp(code);
|
||||
if (app == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return app.getActive();
|
||||
}
|
||||
|
||||
private List<App> getDepends(App app, Boolean active) {
|
||||
|
||||
List<App> apps = new ArrayList<App>();
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
for (App depend : app.getDependsOnSet()) {
|
||||
if (depend.getActive().equals(active)) {
|
||||
apps.add(depend);
|
||||
}
|
||||
}
|
||||
|
||||
return sortApps(apps);
|
||||
}
|
||||
|
||||
private List<String> getNames(List<App> apps) {
|
||||
|
||||
List<String> names = new ArrayList<String>();
|
||||
|
||||
for (App app : apps) {
|
||||
names.add(app.getName());
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private List<App> getChildren(App app, Boolean active) {
|
||||
|
||||
String code = app.getCode();
|
||||
|
||||
String query = "self.dependsOnSet.code = ?1";
|
||||
|
||||
if (active != null) {
|
||||
query = "(" + query + ") AND self.active = " + active;
|
||||
}
|
||||
List<App> apps = appRepo.all().filter(query, code).fetch();
|
||||
|
||||
log.debug("Parent app: {}, Total children: {}", app.getName(), apps.size());
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public App installApp(App app, String language) throws AxelorException {
|
||||
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
if (app.getActive()) {
|
||||
return app;
|
||||
}
|
||||
|
||||
if (language != null) {
|
||||
app.setLanguageSelect(language);
|
||||
} else {
|
||||
language = app.getLanguageSelect();
|
||||
}
|
||||
|
||||
List<App> apps = getDepends(app, false);
|
||||
|
||||
for (App parentApp : apps) {
|
||||
installApp(parentApp, language);
|
||||
}
|
||||
|
||||
log.debug("Init data loaded: {}, for app: {}", app.getInitDataLoaded(), app.getCode());
|
||||
if (!app.getInitDataLoaded()) {
|
||||
app = importDataInit(app);
|
||||
}
|
||||
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
app.setActive(true);
|
||||
|
||||
return saveApp(app);
|
||||
}
|
||||
|
||||
private List<App> sortApps(Collection<App> apps) {
|
||||
|
||||
List<App> appsList = new ArrayList<App>();
|
||||
|
||||
appsList.addAll(apps);
|
||||
|
||||
appsList.sort(
|
||||
new Comparator<App>() {
|
||||
|
||||
@Override
|
||||
public int compare(App app1, App app2) {
|
||||
|
||||
Integer order1 = app1.getInstallOrder();
|
||||
Integer order2 = app2.getInstallOrder();
|
||||
|
||||
if (order1 < order2) {
|
||||
return -1;
|
||||
}
|
||||
if (order1 > order2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
log.debug("Apps sorted: {}", getNames(appsList));
|
||||
|
||||
return appsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshApp() throws IOException {
|
||||
|
||||
File dataDir = Files.createTempDir();
|
||||
File imgDir = new File(dataDir, "img");
|
||||
imgDir.mkdir();
|
||||
|
||||
CSVConfig csvConfig = new CSVConfig();
|
||||
csvConfig.setInputs(new ArrayList<CSVInput>());
|
||||
|
||||
List<MetaModel> metaModels =
|
||||
metaModelRepo
|
||||
.all()
|
||||
.filter(
|
||||
"self.name != 'App' and self.name like 'App%' and self.packageName = ?1",
|
||||
App.class.getPackage().getName())
|
||||
.fetch();
|
||||
|
||||
log.debug("Total app models: {}", metaModels.size());
|
||||
for (MetaModel metaModel : metaModels) {
|
||||
Class<?> klass;
|
||||
try {
|
||||
klass = Class.forName(metaModel.getFullName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
if (!App.class.isAssignableFrom(klass)) {
|
||||
log.debug("Not a App class : {}", metaModel.getName());
|
||||
continue;
|
||||
}
|
||||
Object obj = null;
|
||||
Query query = JPA.em().createQuery("SELECT id FROM " + metaModel.getName());
|
||||
try {
|
||||
obj = query.setMaxResults(1).getSingleResult();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
if (obj != null) {
|
||||
continue;
|
||||
}
|
||||
log.debug("App without app record: {}", metaModel.getName());
|
||||
String csvName = "base_" + inflector.camelize(klass.getSimpleName(), true) + ".csv";
|
||||
String pngName = inflector.dasherize(klass.getSimpleName()) + ".png";
|
||||
CSVInput input = new CSVInput();
|
||||
input.setFileName(csvName);
|
||||
input.setTypeName(klass.getName());
|
||||
input.setCallable("com.axelor.csv.script.ImportApp:importApp");
|
||||
input.setSearch("self.code =:code");
|
||||
input.setSeparator(';');
|
||||
csvConfig.getInputs().add(input);
|
||||
InputStream stream = klass.getResourceAsStream("/data-init/input/" + csvName);
|
||||
copyStream(stream, new File(dataDir, csvName));
|
||||
stream = klass.getResourceAsStream("/data-init/input/img/" + pngName);
|
||||
copyStream(stream, new File(imgDir, pngName));
|
||||
}
|
||||
|
||||
if (!csvConfig.getInputs().isEmpty()) {
|
||||
CSVImporter importer = new CSVImporter(csvConfig, dataDir.getAbsolutePath());
|
||||
if (importer != null) {
|
||||
importer.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copyStream(InputStream stream, File file) throws IOException {
|
||||
|
||||
if (stream != null) {
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
try {
|
||||
ByteStreams.copy(stream, out);
|
||||
out.close();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public App unInstallApp(App app) throws AxelorException {
|
||||
|
||||
List<App> children = getChildren(app, true);
|
||||
if (!children.isEmpty()) {
|
||||
List<String> childrenNames = getNames(children);
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY, IExceptionMessages.APP_IN_USE, childrenNames);
|
||||
}
|
||||
|
||||
app.setActive(false);
|
||||
|
||||
return saveApp(app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkInstall(Collection<App> apps, Boolean importDemo, String language)
|
||||
throws AxelorException {
|
||||
|
||||
apps = sortApps(apps);
|
||||
|
||||
for (App app : apps) {
|
||||
app = installApp(app, language);
|
||||
if (importDemo != null && importDemo) {
|
||||
importDataDemo(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public App importRoles(App app) throws AxelorException {
|
||||
|
||||
if (app.getIsRolesImported()) {
|
||||
return app;
|
||||
}
|
||||
|
||||
importParentRoles(app);
|
||||
|
||||
importData(app, DIR_ROLES, false);
|
||||
|
||||
app = appRepo.find(app.getId());
|
||||
|
||||
app.setIsRolesImported(true);
|
||||
|
||||
return saveApp(app);
|
||||
}
|
||||
|
||||
private void importParentRoles(App app) throws AxelorException {
|
||||
|
||||
List<App> depends = getDepends(app, true);
|
||||
|
||||
for (App parent : depends) {
|
||||
parent = appRepo.find(parent.getId());
|
||||
if (!parent.getIsRolesImported()) {
|
||||
importRoles(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRoles() throws AxelorException {
|
||||
|
||||
List<App> apps = appRepo.all().filter("self.isRolesImported = false").fetch();
|
||||
apps = sortApps(apps);
|
||||
|
||||
for (App app : apps) {
|
||||
importRoles(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,660 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.apps.base.db.DataBackup;
|
||||
import com.axelor.auth.db.AuditableModel;
|
||||
import com.axelor.common.StringUtils;
|
||||
import com.axelor.data.csv.CSVBind;
|
||||
import com.axelor.data.csv.CSVConfig;
|
||||
import com.axelor.data.csv.CSVInput;
|
||||
import com.axelor.db.JpaRepository;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.db.mapper.Property;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaJsonField;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.opencsv.CSVWriter;
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DataBackupCreateService {
|
||||
|
||||
private static final char SEPARATOR = ',';
|
||||
private static final char QUOTE_CHAR = '"';
|
||||
private static final char REFERENCE_FIELD_SEPARATOR = '|';
|
||||
|
||||
private static final int BUFFER_SIZE = 1000;
|
||||
|
||||
@Inject private MetaModelRepository metaModelRepo;
|
||||
|
||||
private Logger LOG = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private boolean notNullReferenceFlag, referenceFlag;
|
||||
private boolean byteArrFieldFlag = false;
|
||||
|
||||
private static Set<String> exceptColumnNameList =
|
||||
ImmutableSet.of(
|
||||
"importOrigin",
|
||||
"importId",
|
||||
"updatedBy",
|
||||
"createdBy",
|
||||
"updatedOn",
|
||||
"createdOn",
|
||||
"archived",
|
||||
"version",
|
||||
"attrs");
|
||||
|
||||
private static Map<Object, Object> AutoImportModelMap =
|
||||
ImmutableMap.builder()
|
||||
.put("com.axelor.apps.base.db.App", "self.code = :code")
|
||||
.put("com.axelor.auth.db.Role", "self.name = :name")
|
||||
.put("com.axelor.auth.db.User", "self.code = :code")
|
||||
.put("com.axelor.auth.db.Permission", "self.name = :name")
|
||||
.put("com.axelor.auth.db.Group", "self.code = :code")
|
||||
.put("com.axelor.apps.base.db.Language", "self.code = :code")
|
||||
.put("com.axelor.apps.base.db.BirtTemplate", "self.name = :name")
|
||||
.put("com.axelor.apps.base.db.BirtTemplateParameter", "self.name = :name")
|
||||
.put("com.axelor.apps.crm.db.EventCategory", "self.code = :code")
|
||||
.put("com.axelor.apps.account.db.AccountChart", "self.code = :code")
|
||||
.put("com.axelor.apps.bankpayment.db.BankOrderFileFormat", "self.name = :name")
|
||||
.put("com.axelor.apps.bankpayment.db.BankStatementFileFormat", "self.name = :name")
|
||||
.build();
|
||||
|
||||
List<String> fileNameList;
|
||||
|
||||
/* Generate csv Files for each individual MetaModel and single config file */
|
||||
public File create(DataBackup dataBackup) throws InterruptedException {
|
||||
File tempDir = Files.createTempDir();
|
||||
String tempDirectoryPath = tempDir.getAbsolutePath();
|
||||
|
||||
fileNameList = new ArrayList<>();
|
||||
List<MetaModel> metaModelList = getMetaModels();
|
||||
|
||||
LinkedList<CSVInput> simpleCsvs = new LinkedList<>();
|
||||
LinkedList<CSVInput> refernceCsvs = new LinkedList<>();
|
||||
LinkedList<CSVInput> notNullReferenceCsvs = new LinkedList<>();
|
||||
Map<String, List<String>> subClassesMap = getSubClassesMap();
|
||||
|
||||
for (MetaModel metaModel : metaModelList) {
|
||||
try {
|
||||
List<String> subClasses = subClassesMap.get(metaModel.getFullName());
|
||||
long totalRecord = getMetaModelDataCount(metaModel, subClasses);
|
||||
if (totalRecord > 0) {
|
||||
LOG.debug("Exporting Model : " + metaModel.getFullName());
|
||||
|
||||
notNullReferenceFlag = false;
|
||||
referenceFlag = false;
|
||||
|
||||
CSVWriter csvWriter =
|
||||
new CSVWriter(
|
||||
new FileWriter(new File(tempDirectoryPath, metaModel.getName() + ".csv")),
|
||||
SEPARATOR,
|
||||
QUOTE_CHAR);
|
||||
CSVInput csvInput =
|
||||
writeCSVData(
|
||||
metaModel, csvWriter, dataBackup, totalRecord, subClasses, tempDirectoryPath);
|
||||
csvWriter.close();
|
||||
|
||||
if (notNullReferenceFlag) {
|
||||
notNullReferenceCsvs.add(csvInput);
|
||||
} else if (referenceFlag) {
|
||||
refernceCsvs.add(csvInput);
|
||||
CSVInput temcsv = new CSVInput();
|
||||
temcsv.setFileName(csvInput.getFileName());
|
||||
temcsv.setTypeName(csvInput.getTypeName());
|
||||
|
||||
if (dataBackup.getIsRelativeDate()) {
|
||||
temcsv.setBindings(new ArrayList<>());
|
||||
getCsvInputForDateorDateTime(metaModel, temcsv);
|
||||
}
|
||||
if (AutoImportModelMap.containsKey(csvInput.getTypeName())) {
|
||||
temcsv.setSearch(AutoImportModelMap.get(csvInput.getTypeName()).toString());
|
||||
}
|
||||
if (Class.forName(metaModel.getFullName()).getSuperclass() == App.class) {
|
||||
temcsv.setSearch("self.code = :code");
|
||||
}
|
||||
simpleCsvs.add(temcsv);
|
||||
} else {
|
||||
simpleCsvs.add(csvInput);
|
||||
}
|
||||
|
||||
fileNameList.add(metaModel.getName() + ".csv");
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (IOException e) {
|
||||
TraceBackService.trace(e, DataBackupService.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
CSVConfig csvConfig = new CSVConfig();
|
||||
csvConfig.setInputs(simpleCsvs);
|
||||
csvConfig.getInputs().addAll(notNullReferenceCsvs);
|
||||
csvConfig.getInputs().addAll(refernceCsvs);
|
||||
csvConfig.getInputs().addAll(notNullReferenceCsvs);
|
||||
generateConfig(tempDirectoryPath, csvConfig);
|
||||
|
||||
fileNameList.add(DataBackupServiceImpl.CONFIG_FILE_NAME);
|
||||
File zippedFile = generateZIP(tempDirectoryPath, fileNameList);
|
||||
return zippedFile;
|
||||
}
|
||||
|
||||
void getCsvInputForDateorDateTime(MetaModel metaModel, CSVInput csvInput) {
|
||||
try {
|
||||
Mapper metaModelMapper = Mapper.of(Class.forName(metaModel.getFullName()));
|
||||
Property[] properties = metaModelMapper.getProperties();
|
||||
for (Property property : properties) {
|
||||
String propertyType = property.getType().toString();
|
||||
if ((propertyType.equals("DATE") || propertyType.equals("DATETIME"))
|
||||
&& !property.getName().equals("createdOn")
|
||||
&& !property.getName().equals("updatedOn")) {
|
||||
getDateOrDateTimeHeader(property, csvInput);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get All MetaModels */
|
||||
private List<MetaModel> getMetaModels() {
|
||||
String filterStr =
|
||||
"self.packageName NOT LIKE '%meta%' AND self.packageName !='com.axelor.studio.db' AND self.name!='DataBackup'";
|
||||
List<MetaModel> metaModels = metaModelRepo.all().filter(filterStr).order("fullName").fetch();
|
||||
metaModels.add(metaModelRepo.findByName(MetaFile.class.getSimpleName()));
|
||||
metaModels.add(metaModelRepo.findByName(MetaJsonField.class.getSimpleName()));
|
||||
return metaModels;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getSubClassesMap() {
|
||||
List<MetaModel> metaModels = getMetaModels();
|
||||
List<String> subClasses;
|
||||
Map<String, List<String>> subClassMap = new HashMap<String, List<String>>();
|
||||
for (MetaModel metaModel : metaModels) {
|
||||
try {
|
||||
subClasses = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AuditableModel> superClass =
|
||||
(Class<AuditableModel>) Class.forName(metaModel.getFullName()).getSuperclass();
|
||||
if (superClass != AuditableModel.class) {
|
||||
if (!subClassMap.isEmpty() && subClassMap.containsKey(superClass.getName())) {
|
||||
subClasses = subClassMap.get(superClass.getName());
|
||||
}
|
||||
subClasses.add(metaModel.getName());
|
||||
subClassMap.put(superClass.getName(), subClasses);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return subClassMap;
|
||||
}
|
||||
|
||||
/* Get All Data of Specific MetaModel */
|
||||
private List<Model> getMetaModelDataList(
|
||||
MetaModel metaModel, int start, Integer fetchLimit, List<String> subClasses)
|
||||
throws ClassNotFoundException {
|
||||
return getQuery(metaModel, subClasses).fetch(fetchLimit, start);
|
||||
}
|
||||
|
||||
private long getMetaModelDataCount(MetaModel metaModel, List<String> subClasses)
|
||||
throws InterruptedException, ClassNotFoundException {
|
||||
Query<Model> query = getQuery(metaModel, subClasses);
|
||||
long count = 0;
|
||||
if (query != null) {
|
||||
count = query.count();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private Query<Model> getQuery(MetaModel metaModel, List<String> subClasses)
|
||||
throws ClassNotFoundException {
|
||||
String whereStr = "";
|
||||
if (subClasses != null && subClasses.size() > 0) {
|
||||
for (String subClassName : subClasses) {
|
||||
whereStr += whereStr.length() > 0 ? " AND " : "";
|
||||
whereStr += "id NOT IN (select id from " + subClassName + ")";
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Model> klass = (Class<Model>) Class.forName(metaModel.getFullName());
|
||||
JpaRepository<Model> model = null;
|
||||
Query<Model> query = null;
|
||||
try {
|
||||
model = JpaRepository.of(klass);
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e, DataBackupService.class.getName());
|
||||
}
|
||||
if (model != null) {
|
||||
query = JpaRepository.of(klass).all();
|
||||
if (StringUtils.notEmpty(whereStr)) {
|
||||
query.filter(whereStr);
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private CSVInput writeCSVData(
|
||||
MetaModel metaModel,
|
||||
CSVWriter csvWriter,
|
||||
DataBackup dataBackup,
|
||||
long totalRecord,
|
||||
List<String> subClasses,
|
||||
String dirPath) {
|
||||
|
||||
CSVInput csvInput = new CSVInput();
|
||||
boolean headerFlag = true;
|
||||
List<String> dataArr = null;
|
||||
List<String> headerArr = new ArrayList<>();
|
||||
List<Model> dataList = null;
|
||||
|
||||
try {
|
||||
Mapper metaModelMapper = Mapper.of(Class.forName(metaModel.getFullName()));
|
||||
Property[] pro = metaModelMapper.getProperties();
|
||||
Integer fetchLimit = dataBackup.getFetchLimit();
|
||||
boolean isRelativeDate = dataBackup.getIsRelativeDate();
|
||||
boolean updateImportId = dataBackup.getUpdateImportId();
|
||||
|
||||
csvInput.setFileName(metaModel.getName() + ".csv");
|
||||
csvInput.setTypeName(metaModel.getFullName());
|
||||
csvInput.setBindings(new ArrayList<>());
|
||||
|
||||
for (int i = 0; i < totalRecord; i = i + fetchLimit) {
|
||||
|
||||
dataList = getMetaModelDataList(metaModel, i, fetchLimit, subClasses);
|
||||
|
||||
if (dataList != null && dataList.size() > 0) {
|
||||
for (Object dataObject : dataList) {
|
||||
dataArr = new ArrayList<>();
|
||||
|
||||
for (Property property : pro) {
|
||||
if (isPropertyExportable(property)) {
|
||||
if (headerFlag) {
|
||||
String headerStr =
|
||||
getMetaModelHeader(dataObject, property, csvInput, isRelativeDate);
|
||||
headerArr.add(headerStr);
|
||||
}
|
||||
dataArr.add(
|
||||
getMetaModelData(
|
||||
metaModel.getName(),
|
||||
metaModelMapper,
|
||||
property,
|
||||
dataObject,
|
||||
dirPath,
|
||||
isRelativeDate,
|
||||
updateImportId));
|
||||
}
|
||||
}
|
||||
|
||||
if (headerFlag) {
|
||||
if (byteArrFieldFlag) {
|
||||
csvInput.setCallable(
|
||||
"com.axelor.apps.base.service.app.DataBackupRestoreService:importObjectWithByteArray");
|
||||
byteArrFieldFlag = false;
|
||||
}
|
||||
csvWriter.writeNext(headerArr.toArray(new String[headerArr.size()]), true);
|
||||
headerFlag = false;
|
||||
}
|
||||
csvWriter.writeNext(dataArr.toArray(new String[dataArr.size()]), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AutoImportModelMap.containsKey(csvInput.getTypeName())) {
|
||||
csvInput.setSearch(AutoImportModelMap.get(csvInput.getTypeName()).toString());
|
||||
} else if (Class.forName(metaModel.getFullName()).getSuperclass() == App.class) {
|
||||
csvInput.setSearch("self.code = :code");
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
return csvInput;
|
||||
}
|
||||
|
||||
private boolean isPropertyExportable(Property property) {
|
||||
if (!exceptColumnNameList.contains(property.getName())
|
||||
&& ((StringUtils.isEmpty(property.getMappedBy()))
|
||||
|| (!StringUtils.isEmpty(property.getMappedBy())
|
||||
&& (property.getTarget() != null
|
||||
&& property
|
||||
.getTarget()
|
||||
.getPackage()
|
||||
.equals(Package.getPackage("com.axelor.meta.db"))
|
||||
&& !property.getTarget().isAssignableFrom(MetaFile.class)
|
||||
&& !property.getTarget().isAssignableFrom(MetaJsonField.class))))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get Header For csv File */
|
||||
private String getMetaModelHeader(
|
||||
Object value, Property property, CSVInput csvInput, boolean isRelativeDate) {
|
||||
String propertyTypeStr = property.getType().toString();
|
||||
String propertyName = property.getName();
|
||||
switch (propertyTypeStr) {
|
||||
case "DATE":
|
||||
case "DATETIME":
|
||||
if (isRelativeDate) {
|
||||
return getDateOrDateTimeHeader(property, csvInput);
|
||||
}
|
||||
return propertyName;
|
||||
case "LONG":
|
||||
return propertyName.equalsIgnoreCase("id") ? "importId" : propertyName;
|
||||
case "BINARY":
|
||||
byteArrFieldFlag = true;
|
||||
return "byte_" + propertyName;
|
||||
case "ONE_TO_ONE":
|
||||
case "MANY_TO_ONE":
|
||||
return getRelationalFieldHeader(property, csvInput, "ONE");
|
||||
case "ONE_TO_MANY":
|
||||
case "MANY_TO_MANY":
|
||||
return getRelationalFieldHeader(property, csvInput, "MANY");
|
||||
default:
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
private String getDateOrDateTimeHeader(Property property, CSVInput csvInput) {
|
||||
String propertyName = property.getName();
|
||||
CSVBind csvBind = new CSVBind();
|
||||
csvBind.setField(propertyName);
|
||||
csvBind.setColumn(propertyName);
|
||||
if (property.getType().toString().equals("DATE")) {
|
||||
csvBind.setExpression(
|
||||
"call:com.axelor.csv.script.ImportDateTime:importDate(" + propertyName + ")");
|
||||
|
||||
} else {
|
||||
csvBind.setExpression(
|
||||
"call:com.axelor.csv.script.ImportDateTime:importDateTime(" + propertyName + ")");
|
||||
}
|
||||
csvInput.getBindings().add(csvBind);
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
private String getRelationalFieldHeader(
|
||||
Property property, CSVInput csvInput, String relationship) {
|
||||
csvInput.setSearch("self.importId = :importId");
|
||||
CSVBind csvBind = new CSVBind();
|
||||
String columnName = property.getName() + "_importId";
|
||||
String search =
|
||||
relationship.equalsIgnoreCase("ONE")
|
||||
? "self.importId = :" + columnName
|
||||
: "self.importId in :" + columnName;
|
||||
if (property.getTarget() != null
|
||||
&& property.getTarget().getPackage().equals(Package.getPackage("com.axelor.meta.db"))
|
||||
&& !property.getTarget().getTypeName().equals("com.axelor.meta.db.MetaFile")) {
|
||||
columnName = property.getName() + "_name";
|
||||
search =
|
||||
relationship.equalsIgnoreCase("ONE")
|
||||
? "self.name = :" + columnName
|
||||
: "self.name in :" + columnName;
|
||||
}
|
||||
csvBind.setColumn(columnName);
|
||||
csvBind.setField(property.getName());
|
||||
csvBind.setSearch(search);
|
||||
csvBind.setUpdate(true);
|
||||
if (relationship.equalsIgnoreCase("MANY")) {
|
||||
csvBind.setExpression(columnName + ".split('\\\\|') as List");
|
||||
}
|
||||
csvInput.getBindings().add(csvBind);
|
||||
referenceFlag = true;
|
||||
if (property.isRequired()) {
|
||||
notNullReferenceFlag = true;
|
||||
}
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/* Get Data For csv File */
|
||||
private String getMetaModelData(
|
||||
String metaModelName,
|
||||
Mapper metaModelMapper,
|
||||
Property property,
|
||||
Object dataObject,
|
||||
String dirPath,
|
||||
boolean isRelativeDate,
|
||||
boolean updateImportId) {
|
||||
|
||||
String id = metaModelMapper.get(dataObject, "id").toString();
|
||||
Object value = metaModelMapper.get(dataObject, property.getName());
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
String propertyTypeStr = property.getType().toString();
|
||||
|
||||
switch (propertyTypeStr) {
|
||||
case "LONG":
|
||||
if (updateImportId) {
|
||||
return ((Model) dataObject).getImportId();
|
||||
}
|
||||
return value.toString();
|
||||
case "DATE":
|
||||
if (isRelativeDate) {
|
||||
return createRelativeDate((LocalDate) value);
|
||||
}
|
||||
return value.toString();
|
||||
|
||||
case "DATETIME":
|
||||
if (isRelativeDate) {
|
||||
if (property.getJavaType() == ZonedDateTime.class) {
|
||||
return createRelativeDateTime(((ZonedDateTime) value).toLocalDateTime());
|
||||
}
|
||||
return createRelativeDateTime((LocalDateTime) value);
|
||||
}
|
||||
return property.getJavaType() == ZonedDateTime.class
|
||||
? ((ZonedDateTime) value).toLocalDateTime().toString()
|
||||
: value.toString();
|
||||
|
||||
case "BINARY":
|
||||
String fileName = metaModelName + "_" + property.getName() + "_" + id + ".png";
|
||||
|
||||
try {
|
||||
org.apache.commons.io.FileUtils.writeByteArrayToFile(
|
||||
new File(dirPath, fileName), (byte[]) value);
|
||||
fileNameList.add(fileName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fileName;
|
||||
case "ONE_TO_ONE":
|
||||
case "MANY_TO_ONE":
|
||||
return getRelationalFieldValue(property, value, updateImportId);
|
||||
case "ONE_TO_MANY":
|
||||
case "MANY_TO_MANY":
|
||||
return getRelationalFieldData(property, value, updateImportId);
|
||||
default:
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public String createRelativeDateTime(LocalDateTime dateT) {
|
||||
LocalDateTime currentDateTime = LocalDateTime.now();
|
||||
|
||||
long years = currentDateTime.until(dateT, ChronoUnit.YEARS);
|
||||
currentDateTime = currentDateTime.plusYears(years);
|
||||
|
||||
long months = currentDateTime.until(dateT, ChronoUnit.MONTHS);
|
||||
currentDateTime = currentDateTime.plusMonths(months);
|
||||
|
||||
long days = currentDateTime.until(dateT, ChronoUnit.DAYS);
|
||||
currentDateTime = currentDateTime.plusDays(days);
|
||||
|
||||
long hours = currentDateTime.until(dateT, ChronoUnit.HOURS);
|
||||
currentDateTime = currentDateTime.plusHours(hours);
|
||||
|
||||
long minutes = currentDateTime.until(dateT, ChronoUnit.MINUTES);
|
||||
currentDateTime = currentDateTime.plusMinutes(minutes);
|
||||
|
||||
long seconds = currentDateTime.until(dateT, ChronoUnit.SECONDS);
|
||||
if (seconds < 0 || minutes < 0 || hours < 0 || days < 0 || months < 0 || years < 0) {
|
||||
return "NOW["
|
||||
+ ((years == 0) ? "" : (years + "y"))
|
||||
+ ((months == 0) ? "" : (months + "M"))
|
||||
+ ((days == 0) ? "" : (days + "d"))
|
||||
+ ((hours == 0) ? "" : (hours + "H"))
|
||||
+ ((minutes == 0) ? "" : (minutes + "m"))
|
||||
+ ((seconds == 0) ? "" : (seconds + "s"))
|
||||
+ "]";
|
||||
}
|
||||
return "NOW["
|
||||
+ ((years == 0) ? "" : ("+" + years + "y"))
|
||||
+ ((months == 0) ? "" : ("+" + months + "M"))
|
||||
+ ((days == 0) ? "" : ("+" + days + "d"))
|
||||
+ ((hours == 0) ? "" : ("+" + hours + "H"))
|
||||
+ ((minutes == 0) ? "" : ("+" + minutes + "m"))
|
||||
+ ((seconds == 0) ? "" : ("+" + seconds + "s"))
|
||||
+ "]";
|
||||
}
|
||||
|
||||
public String createRelativeDate(LocalDate date) {
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
long years = currentDate.until(date, ChronoUnit.YEARS);
|
||||
currentDate = currentDate.plusYears(years);
|
||||
|
||||
long months = currentDate.until(date, ChronoUnit.MONTHS);
|
||||
currentDate = currentDate.plusMonths(months);
|
||||
|
||||
long days = currentDate.until(date, ChronoUnit.DAYS);
|
||||
currentDate = currentDate.plusDays(days);
|
||||
|
||||
if (days < 0 || months < 0 || years < 0) {
|
||||
return "TODAY["
|
||||
+ ((years == 0) ? "" : years + "y")
|
||||
+ ((months == 0) ? "" : months + "M")
|
||||
+ ((days == 0) ? "" : days + "d")
|
||||
+ "]";
|
||||
} else if (days == 0 && months == 0 && years == 0) {
|
||||
return "TODAY";
|
||||
}
|
||||
return "TODAY["
|
||||
+ ((years == 0) ? "" : ("+" + years + "y"))
|
||||
+ ((months == 0) ? "" : ("+" + months + "M"))
|
||||
+ ((days == 0) ? "" : ("+" + days + "d"))
|
||||
+ "]";
|
||||
}
|
||||
|
||||
public String getRelationalFieldData(Property property, Object value, boolean updateImportId) {
|
||||
StringBuilder idStringBuilder = new StringBuilder();
|
||||
Collection<?> valueList = (Collection<?>) value;
|
||||
String referenceData = "";
|
||||
for (Object val : valueList) {
|
||||
referenceData = getRelationalFieldValue(property, val, updateImportId);
|
||||
if (StringUtils.notBlank(referenceData)) {
|
||||
idStringBuilder.append(referenceData + REFERENCE_FIELD_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.notBlank(idStringBuilder)) {
|
||||
idStringBuilder.setLength(idStringBuilder.length() - 1);
|
||||
}
|
||||
return idStringBuilder.toString();
|
||||
}
|
||||
|
||||
private String getRelationalFieldValue(Property property, Object val, boolean updateImportId) {
|
||||
if (property.getTarget() != null
|
||||
&& property.getTarget().getPackage().equals(Package.getPackage("com.axelor.meta.db"))
|
||||
&& !property.getTarget().getTypeName().equals("com.axelor.meta.db.MetaFile")) {
|
||||
try {
|
||||
return Mapper.of(val.getClass()).get(val, "name").toString();
|
||||
} catch (Exception e) {
|
||||
return (updateImportId) ? ((Model) val).getImportId() : ((Model) val).getId().toString();
|
||||
}
|
||||
} else {
|
||||
return (updateImportId) ? ((Model) val).getImportId() : ((Model) val).getId().toString();
|
||||
}
|
||||
}
|
||||
|
||||
private File generateZIP(String dirPath, List<String> fileNameList) {
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmSS");
|
||||
String backupZipFileName = "DataBackup_" + LocalDateTime.now().format(formatter) + ".zip";
|
||||
File zipFile = new File(dirPath, backupZipFileName);
|
||||
try {
|
||||
ZipOutputStream out = null;
|
||||
out = new ZipOutputStream(new FileOutputStream(zipFile));
|
||||
for (String fileName : fileNameList) {
|
||||
ZipEntry e = new ZipEntry(fileName);
|
||||
out.putNextEntry(e);
|
||||
File file = new File(dirPath, fileName);
|
||||
BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file));
|
||||
byte[] data;
|
||||
while (bin.available() > 0) {
|
||||
if (bin.available() < BUFFER_SIZE) {
|
||||
data = new byte[bin.available()];
|
||||
} else {
|
||||
data = new byte[BUFFER_SIZE];
|
||||
}
|
||||
bin.read(data, 0, data.length);
|
||||
out.write(data, 0, data.length);
|
||||
}
|
||||
bin.close();
|
||||
out.closeEntry();
|
||||
file.delete();
|
||||
}
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
TraceBackService.trace(e, "Error From DataBackupCreateService - generateZIP()");
|
||||
}
|
||||
return zipFile;
|
||||
}
|
||||
|
||||
/* Generate XML File from CSVConfig */
|
||||
private void generateConfig(String dirPath, CSVConfig csvConfig) {
|
||||
try {
|
||||
File file = new File(dirPath, DataBackupServiceImpl.CONFIG_FILE_NAME);
|
||||
FileWriter fileWriter = new FileWriter(file, true);
|
||||
|
||||
XStream xStream = new XStream();
|
||||
xStream.processAnnotations(CSVConfig.class);
|
||||
xStream.setMode(XStream.NO_REFERENCES);
|
||||
fileWriter.append(xStream.toXML(csvConfig));
|
||||
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
TraceBackService.trace(e, "Error From DataBackupCreateService - generateConfig()");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.common.StringUtils;
|
||||
import com.axelor.data.Listener;
|
||||
import com.axelor.data.csv.CSVImporter;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.db.mapper.Mapper;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.Files;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DataBackupRestoreService {
|
||||
|
||||
/* Restore the Data using provided zip File and prepare Log File and Return it*/
|
||||
public File restore(MetaFile zipedBackupFile) {
|
||||
Logger LOG = LoggerFactory.getLogger(getClass());
|
||||
File tempDir = Files.createTempDir();
|
||||
String dirPath = tempDir.getAbsolutePath();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
unZip(zipedBackupFile, dirPath);
|
||||
String configFName =
|
||||
tempDir.getAbsolutePath() + File.separator + DataBackupServiceImpl.CONFIG_FILE_NAME;
|
||||
|
||||
CSVImporter csvImporter = new CSVImporter(configFName, tempDir.getAbsolutePath());
|
||||
csvImporter.addListener(
|
||||
new Listener() {
|
||||
String modelName;
|
||||
StringBuilder sb1 = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void handle(Model bean, Exception e) {
|
||||
if (e.getMessage() != null && !e.getMessage().equals("null")) {
|
||||
if (bean != null) {
|
||||
sb1.append(bean.getClass().getSimpleName() + " : \n" + e.getMessage() + "\n\n");
|
||||
} else {
|
||||
sb1.append(e.getMessage() + "\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void imported(Model model) {
|
||||
modelName = model.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void imported(Integer total, Integer count) {
|
||||
String str = "", strError = "";
|
||||
if (!StringUtils.isBlank(sb1)) {
|
||||
strError = "Errors : \n" + sb1.toString();
|
||||
}
|
||||
str = "Total Records : {" + total + "} - Success Records : {" + count + "} \n";
|
||||
if (total != 0 && count != 0) {
|
||||
sb.append(modelName + " : \n");
|
||||
}
|
||||
sb.append(strError).append(str + "-----------------------------------------\n");
|
||||
sb1.setLength(0);
|
||||
}
|
||||
});
|
||||
csvImporter.run();
|
||||
LOG.info("Data Restore Completed");
|
||||
FileUtils.cleanDirectory(new File(tempDir.getAbsolutePath()));
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmSS");
|
||||
String logFileName = "DataBackupLog_" + LocalDateTime.now().format(formatter) + ".log";
|
||||
|
||||
File file = new File(tempDir.getAbsolutePath(), logFileName);
|
||||
PrintWriter pw = new PrintWriter(file);
|
||||
pw.write(sb.toString());
|
||||
pw.close();
|
||||
return file;
|
||||
} catch (IOException e) {
|
||||
TraceBackService.trace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean unZip(MetaFile zipMetaFile, String destinationDirectoryPath) throws IOException {
|
||||
File zipFile = MetaFiles.getPath(zipMetaFile).toFile();
|
||||
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile)));
|
||||
ZipEntry ze;
|
||||
byte[] buffer = new byte[1024];
|
||||
int count;
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
FileOutputStream fout =
|
||||
new FileOutputStream(new File(destinationDirectoryPath, ze.getName()));
|
||||
while ((count = zis.read(buffer)) != -1) {
|
||||
fout.write(buffer, 0, count);
|
||||
}
|
||||
fout.close();
|
||||
zis.closeEntry();
|
||||
}
|
||||
zis.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean SeuencesExist() {
|
||||
long total = (long) JPA.em().createQuery("SELECT count(*) FROM Sequence").getSingleResult();
|
||||
long total1 = (long) JPA.em().createQuery("SELECT count(*) FROM MrpLineType").getSingleResult();
|
||||
return total > 0 || total1 > 0 ? true : false;
|
||||
}
|
||||
|
||||
public Object importObjectWithByteArray(Object bean, Map<String, Object> values)
|
||||
throws IOException {
|
||||
assert bean instanceof Model;
|
||||
final Path path = (Path) values.get("__path__");
|
||||
|
||||
Mapper mapper = Mapper.of(bean.getClass());
|
||||
for (String fieldName : values.keySet()) {
|
||||
if (fieldName.startsWith("byte_")) {
|
||||
String fileName = (String) values.get(fieldName);
|
||||
if (Strings.isNullOrEmpty((fileName))) {
|
||||
return bean;
|
||||
}
|
||||
try {
|
||||
final File image = path.resolve(fileName).toFile();
|
||||
byte[] bytes = new byte[(int) image.length()];
|
||||
bytes = java.nio.file.Files.readAllBytes(image.toPath());
|
||||
mapper.set(bean, fieldName.substring(5), bytes);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.DataBackup;
|
||||
|
||||
public interface DataBackupService {
|
||||
|
||||
public void createBackUp(DataBackup dataBackup);
|
||||
|
||||
public void restoreBackUp(DataBackup dataBackup);
|
||||
|
||||
public boolean SeuencesExist();
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.service.app;
|
||||
|
||||
import com.axelor.apps.base.db.DataBackup;
|
||||
import com.axelor.apps.base.db.repo.DataBackupRepository;
|
||||
import com.axelor.auth.AuditableRunner;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaJsonField;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.google.inject.servlet.RequestScoper;
|
||||
import com.google.inject.servlet.ServletScopes;
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DataBackupServiceImpl implements DataBackupService {
|
||||
|
||||
static final String CONFIG_FILE_NAME = "config.xml";
|
||||
|
||||
@Inject private DataBackupCreateService createService;
|
||||
|
||||
@Inject private DataBackupRestoreService restoreService;
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
@Inject private DataBackupRepository dataBackupRepository;
|
||||
|
||||
@Inject private MetaModelRepository metaModelRepo;
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void createBackUp(DataBackup dataBackup) {
|
||||
DataBackup obj = dataBackupRepository.find(dataBackup.getId());
|
||||
obj.setStatusSelect(DataBackupRepository.DATA_BACKUP_STATUS_IN_PROGRESS);
|
||||
dataBackupRepository.save(obj);
|
||||
if (dataBackup.getUpdateImportId()) {
|
||||
updateImportId();
|
||||
}
|
||||
try {
|
||||
executor.submit(
|
||||
new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
|
||||
try (RequestScoper.CloseableScope ignored = scope.open()) {
|
||||
startBackup(obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
protected void startBackup(DataBackup dataBackup) throws Exception {
|
||||
final AuditableRunner runner = Beans.get(AuditableRunner.class);
|
||||
final Callable<Boolean> job =
|
||||
new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
Logger LOG = LoggerFactory.getLogger(getClass());
|
||||
DataBackup obj = Beans.get(DataBackupRepository.class).find(dataBackup.getId());
|
||||
File backupFile = createService.create(obj);
|
||||
dataBackupRepository.refresh(obj);
|
||||
obj.setBackupMetaFile(metaFiles.upload(backupFile));
|
||||
obj.setStatusSelect(DataBackupRepository.DATA_BACKUP_STATUS_CREATED);
|
||||
Beans.get(DataBackupRepository.class).save(obj);
|
||||
LOG.info("Data BackUp Saved");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
runner.run(job);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void restoreBackUp(DataBackup dataBackup) {
|
||||
DataBackup obj = dataBackupRepository.find(dataBackup.getId());
|
||||
obj.setStatusSelect(DataBackupRepository.DATA_BACKUP_STATUS_IN_PROGRESS);
|
||||
dataBackupRepository.save(obj);
|
||||
|
||||
try {
|
||||
executor.submit(
|
||||
new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
startRestore(dataBackup);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void startRestore(DataBackup dataBackup) throws Exception {
|
||||
final AuditableRunner runner = Beans.get(AuditableRunner.class);
|
||||
final Callable<Boolean> job =
|
||||
new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
Logger LOG = LoggerFactory.getLogger(getClass());
|
||||
DataBackup obj = Beans.get(DataBackupRepository.class).find(dataBackup.getId());
|
||||
File logFile = restoreService.restore(obj.getBackupMetaFile());
|
||||
save(logFile, obj);
|
||||
LOG.info("Data Restore Saved");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void save(File logFile, DataBackup obj) {
|
||||
try {
|
||||
JPA.em().getTransaction().begin();
|
||||
obj = Beans.get(DataBackupRepository.class).find(dataBackup.getId());
|
||||
if (logFile != null) {
|
||||
obj.setStatusSelect(DataBackupRepository.DATA_BACKUP_STATUS_RESTORE);
|
||||
obj.setLogMetaFile(metaFiles.upload(logFile));
|
||||
} else {
|
||||
obj.setStatusSelect(DataBackupRepository.DATA_BACKUP_STATUS_RESTORE_ERROR);
|
||||
}
|
||||
Beans.get(DataBackupRepository.class).save(obj);
|
||||
JPA.em().getTransaction().commit();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
runner.run(job);
|
||||
}
|
||||
|
||||
public boolean SeuencesExist() {
|
||||
return restoreService.SeuencesExist();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateImportId() {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddMMyyHHmm");
|
||||
String filterStr =
|
||||
"self.packageName NOT LIKE '%meta%' AND self.packageName !='com.axelor.studio.db' AND self.name!='DataBackup'";
|
||||
|
||||
List<MetaModel> metaModelList = metaModelRepo.all().filter(filterStr).fetch();
|
||||
metaModelList.add(metaModelRepo.findByName(MetaFile.class.getSimpleName()));
|
||||
metaModelList.add(metaModelRepo.findByName(MetaJsonField.class.getSimpleName()));
|
||||
|
||||
for (MetaModel metaModel : metaModelList) {
|
||||
String currentDateTimeStr = "'" + LocalDateTime.now().format(formatter).toString() + "'";
|
||||
String query =
|
||||
"Update "
|
||||
+ metaModel.getName()
|
||||
+ " self SET self.importId = CONCAT(CAST(self.id as text),"
|
||||
+ currentDateTimeStr
|
||||
+ ") WHERE self.importId=null";
|
||||
JPA.execute(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.web;
|
||||
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.apps.base.db.repo.AppRepository;
|
||||
import com.axelor.apps.base.exceptions.IExceptionMessages;
|
||||
import com.axelor.apps.base.service.app.AccessConfigImportService;
|
||||
import com.axelor.apps.base.service.app.AccessTemplateService;
|
||||
import com.axelor.apps.base.service.app.AppService;
|
||||
import com.axelor.common.Inflector;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.repo.MetaFileRepository;
|
||||
import com.axelor.meta.db.repo.MetaViewRepository;
|
||||
import com.axelor.meta.schema.actions.ActionView;
|
||||
import com.axelor.rpc.ActionRequest;
|
||||
import com.axelor.rpc.ActionResponse;
|
||||
import com.axelor.rpc.Context;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class AppController {
|
||||
|
||||
public void importDataDemo(ActionRequest request, ActionResponse response)
|
||||
throws AxelorException {
|
||||
|
||||
App app = request.getContext().asType(App.class);
|
||||
app = Beans.get(AppRepository.class).find(app.getId());
|
||||
Beans.get(AppService.class).importDataDemo(app);
|
||||
|
||||
response.setFlash(I18n.get(IExceptionMessages.DEMO_DATA_SUCCESS));
|
||||
|
||||
response.setReload(true);
|
||||
}
|
||||
|
||||
public void installApp(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
App app = request.getContext().asType(App.class);
|
||||
app = Beans.get(AppRepository.class).find(app.getId());
|
||||
|
||||
Beans.get(AppService.class).installApp(app, null);
|
||||
|
||||
response.setSignal("refresh-app", true);
|
||||
}
|
||||
|
||||
public void configure(ActionRequest request, ActionResponse response) {
|
||||
|
||||
App app = request.getContext().asType(App.class);
|
||||
|
||||
String code = app.getCode();
|
||||
String appName = Inflector.getInstance().camelize(code);
|
||||
String viewName = "app-" + code + "-config-form";
|
||||
|
||||
if (Beans.get(MetaViewRepository.class).findByName(viewName) == null) {
|
||||
response.setFlash(I18n.get(IExceptionMessages.NO_CONFIG_REQUIRED));
|
||||
} else {
|
||||
response.setView(
|
||||
ActionView.define(I18n.get("Configure") + ": " + app.getName())
|
||||
.add("form", viewName)
|
||||
.model("com.axelor.apps.base.db.App" + appName)
|
||||
.context("_showRecord", app.getId())
|
||||
.param("forceEdit", "true")
|
||||
.map());
|
||||
}
|
||||
}
|
||||
|
||||
public void uninstallApp(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
App app = request.getContext().asType(App.class);
|
||||
app = Beans.get(AppRepository.class).find(app.getId());
|
||||
|
||||
Beans.get(AppService.class).unInstallApp(app);
|
||||
|
||||
response.setSignal("refresh-app", true);
|
||||
}
|
||||
|
||||
public void bulkInstall(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
Context context = request.getContext();
|
||||
|
||||
Set<Map<String, Object>> apps = new HashSet<Map<String, Object>>();
|
||||
Collection<Map<String, Object>> appsSet =
|
||||
(Collection<Map<String, Object>>) context.get("appsSet");
|
||||
if (appsSet != null) {
|
||||
apps.addAll(appsSet);
|
||||
}
|
||||
|
||||
Boolean importDemo = (Boolean) context.get("importDemoData");
|
||||
String language = (String) context.get("languageSelect");
|
||||
AppRepository appRepository = Beans.get(AppRepository.class);
|
||||
|
||||
List<App> appList = new ArrayList<App>();
|
||||
for (Map<String, Object> appData : apps) {
|
||||
App app = appRepository.find(Long.parseLong(appData.get("id").toString()));
|
||||
appList.add(app);
|
||||
}
|
||||
|
||||
Beans.get(AppService.class).bulkInstall(appList, importDemo, language);
|
||||
|
||||
response.setFlash(I18n.get(IExceptionMessages.BULK_INSTALL_SUCCESS));
|
||||
response.setSignal("refresh-app", true);
|
||||
}
|
||||
|
||||
public void refreshApp(ActionRequest request, ActionResponse response) {
|
||||
|
||||
try {
|
||||
Beans.get(AppService.class).refreshApp();
|
||||
response.setNotify(I18n.get(IExceptionMessages.REFRESH_APP_SUCCESS));
|
||||
response.setReload(true);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
response.setNotify(I18n.get(IExceptionMessages.REFRESH_APP_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
public void generateAccessTemplate(ActionRequest request, ActionResponse response)
|
||||
throws AxelorException {
|
||||
|
||||
MetaFile accesssFile = Beans.get(AccessTemplateService.class).generateTemplate();
|
||||
|
||||
if (accesssFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
response.setView(
|
||||
ActionView.define(I18n.get("Export file"))
|
||||
.model(App.class.getName())
|
||||
.add(
|
||||
"html",
|
||||
"ws/rest/com.axelor.meta.db.MetaFile/"
|
||||
+ accesssFile.getId()
|
||||
+ "/content/download?v="
|
||||
+ accesssFile.getVersion())
|
||||
.param("download", "true")
|
||||
.map());
|
||||
}
|
||||
|
||||
public void importRoles(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
App app = request.getContext().asType(App.class);
|
||||
|
||||
app = Beans.get(AppRepository.class).find(app.getId());
|
||||
|
||||
Beans.get(AppService.class).importRoles(app);
|
||||
response.setReload(true);
|
||||
response.setFlash(I18n.get(IExceptionMessages.ROLE_IMPORT_SUCCESS));
|
||||
}
|
||||
|
||||
public void importAllRoles(ActionRequest request, ActionResponse response)
|
||||
throws AxelorException {
|
||||
|
||||
Beans.get(AppService.class).importRoles();
|
||||
|
||||
response.setFlash(I18n.get(IExceptionMessages.ROLE_IMPORT_SUCCESS));
|
||||
response.setReload(true);
|
||||
}
|
||||
|
||||
public void importAccessConfig(ActionRequest request, ActionResponse response)
|
||||
throws AxelorException {
|
||||
|
||||
Map<String, Object> metaFileMap = (Map<String, Object>) request.getContext().get("metaFile");
|
||||
|
||||
if (metaFileMap != null) {
|
||||
Long fileId = Long.parseLong(metaFileMap.get("id").toString());
|
||||
Beans.get(AccessConfigImportService.class)
|
||||
.importAccessConfig(Beans.get(MetaFileRepository.class).find(fileId));
|
||||
response.setFlash(I18n.get(IExceptionMessages.ACCESS_CONFIG_IMPORTED));
|
||||
response.setCanClose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.web;
|
||||
|
||||
import com.axelor.apps.base.db.DataBackup;
|
||||
import com.axelor.apps.base.service.app.DataBackupService;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.rpc.ActionRequest;
|
||||
import com.axelor.rpc.ActionResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class DataBackupController {
|
||||
|
||||
public void CreateBackUp(ActionRequest req, ActionResponse res) throws IOException {
|
||||
DataBackup dataBackup = req.getContext().asType(DataBackup.class);
|
||||
Beans.get(DataBackupService.class).createBackUp(dataBackup);
|
||||
res.setReload(true);
|
||||
}
|
||||
|
||||
public void RestoreBackUp(ActionRequest req, ActionResponse res) throws IOException {
|
||||
DataBackupService dataBackupService = Beans.get(DataBackupService.class);
|
||||
DataBackup dataBackup = req.getContext().asType(DataBackup.class);
|
||||
if (dataBackupService.SeuencesExist()) {
|
||||
res.setError("Remove all Sequences And MrpLineTypes to restore backup");
|
||||
} else {
|
||||
dataBackupService.restoreBackUp(dataBackup);
|
||||
}
|
||||
res.setReload(true);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.apps.base.web;
|
||||
|
||||
import com.axelor.apps.base.db.ObjectDataConfig;
|
||||
import com.axelor.apps.base.db.ObjectDataConfigExport;
|
||||
import com.axelor.apps.base.db.repo.ObjectDataConfigRepository;
|
||||
import com.axelor.apps.base.service.ObjectDataAnonymizeService;
|
||||
import com.axelor.apps.base.service.ObjectDataExportService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.schema.actions.ActionView;
|
||||
import com.axelor.rpc.ActionRequest;
|
||||
import com.axelor.rpc.ActionResponse;
|
||||
import com.axelor.rpc.Context;
|
||||
|
||||
public class ObjectDataExportController {
|
||||
|
||||
public void export(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
ObjectDataConfigExport objDataConfigExport =
|
||||
request.getContext().asType(ObjectDataConfigExport.class);
|
||||
|
||||
Long objectDataconfigId = objDataConfigExport.getObjectDataConfig().getId();
|
||||
|
||||
ObjectDataConfig objectDataConfig =
|
||||
Beans.get(ObjectDataConfigRepository.class).find(objectDataconfigId);
|
||||
MetaFile dataFile =
|
||||
Beans.get(ObjectDataExportService.class).export(objectDataConfig, objDataConfigExport);
|
||||
|
||||
if (dataFile != null) {
|
||||
response.setView(
|
||||
ActionView.define(I18n.get("Data"))
|
||||
.add(
|
||||
"html",
|
||||
"ws/rest/com.axelor.meta.db.MetaFile/"
|
||||
+ dataFile.getId()
|
||||
+ "/content/download?v="
|
||||
+ dataFile.getVersion())
|
||||
.param("download", "true")
|
||||
.map());
|
||||
}
|
||||
|
||||
response.setCanClose(true);
|
||||
}
|
||||
|
||||
public void anonymize(ActionRequest request, ActionResponse response) throws AxelorException {
|
||||
|
||||
Context context = request.getContext();
|
||||
Long recordId = Long.parseLong(context.get("modelSelectId").toString());
|
||||
Long objectDataconfigId = Long.parseLong(context.get("objectDataConfigId").toString());
|
||||
ObjectDataConfig objectDataConfig =
|
||||
Beans.get(ObjectDataConfigRepository.class).find(objectDataconfigId);
|
||||
|
||||
Beans.get(ObjectDataAnonymizeService.class).anonymize(objectDataConfig, recordId);
|
||||
|
||||
response.setFlash("Data anonymized successfully");
|
||||
|
||||
response.setCanClose(true);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.auth.db;
|
||||
|
||||
public interface IMessage {
|
||||
|
||||
/** Common messages * */
|
||||
public static final String IMPORT_OK = /*$$(*/ "Import completed successfully" /*)*/;
|
||||
|
||||
public static final String ERR_IMPORT = /*$$(*/ "Error in import. Please check log." /*)*/;
|
||||
|
||||
/** Permission assistant & group menu assistant* */
|
||||
public static final String BAD_FILE = /*$$(*/ "Bad import file" /*)*/;
|
||||
|
||||
public static final String NO_HEADER = /*$$(*/ "No header row found" /*)*/;
|
||||
public static final String BAD_HEADER = /*$$(*/ "Bad header row:" /*)*/;
|
||||
public static final String NO_GROUP = /*$$(*/ "Groups not found: %s" /*)*/;
|
||||
public static final String NO_ROLE = /*$$(*/ "Roles not found: %s" /*)*/;
|
||||
public static final String NO_OBJECT = /*$$(*/ "Object not found: %s" /*)*/;
|
||||
public static final String ERR_IMPORT_WITH_MSG = /*$$(*/
|
||||
"Error in import: %s. Please check the server log" /*)*/;
|
||||
public static final String NO_MENU = /*$$(*/ "Menu not found: %s" /*)*/;
|
||||
}
|
||||
@ -0,0 +1,748 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.auth.service;
|
||||
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.auth.db.Group;
|
||||
import com.axelor.auth.db.IMessage;
|
||||
import com.axelor.auth.db.Permission;
|
||||
import com.axelor.auth.db.PermissionAssistant;
|
||||
import com.axelor.auth.db.Role;
|
||||
import com.axelor.auth.db.repo.GroupRepository;
|
||||
import com.axelor.auth.db.repo.PermissionAssistantRepository;
|
||||
import com.axelor.auth.db.repo.PermissionRepository;
|
||||
import com.axelor.auth.db.repo.RoleRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaField;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.MetaPermission;
|
||||
import com.axelor.meta.db.MetaPermissionRule;
|
||||
import com.axelor.meta.db.repo.MetaFieldRepository;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.axelor.meta.db.repo.MetaPermissionRepository;
|
||||
import com.axelor.meta.db.repo.MetaPermissionRuleRepository;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.opencsv.CSVReader;
|
||||
import com.opencsv.CSVWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.io.output.FileWriterWithEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PermissionAssistantService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Inject private MetaPermissionRepository metaPermissionRepository;
|
||||
|
||||
@Inject private PermissionRepository permissionRepository;
|
||||
|
||||
@Inject private MetaPermissionRuleRepository ruleRepository;
|
||||
|
||||
@Inject private GroupRepository groupRepository;
|
||||
|
||||
@Inject private MetaModelRepository modelRepository;
|
||||
|
||||
@Inject private MetaFieldRepository fieldRepository;
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
@Inject private RoleRepository roleRepo;
|
||||
|
||||
private String errorLog = "";
|
||||
|
||||
private Collection<String> header =
|
||||
Arrays.asList(/*$$(*/ "Object" /*)*/, /*$$(*/ "Field" /*)*/, /*$$(*/ "Title" /*)*/);
|
||||
|
||||
private Collection<String> groupHeader =
|
||||
Arrays.asList(
|
||||
"",
|
||||
/*$$(*/ "Read" /*)*/,
|
||||
/*$$(*/ "Write" /*)*/,
|
||||
/*$$(*/ "Create" /*)*/,
|
||||
/*$$(*/ "Delete" /*)*/,
|
||||
/*$$(*/ "Export" /*)*/,
|
||||
/*$$(*/ "Condition" /*)*/,
|
||||
/*$$(*/ "ConditionParams" /*)*/,
|
||||
/*$$(*/ "Readonly If" /*)*/,
|
||||
/*$$(*/ "Hide If" /*)*/);
|
||||
|
||||
private String getFileName(PermissionAssistant assistant) {
|
||||
|
||||
String userCode = assistant.getCreatedBy().getCode();
|
||||
String dateString = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
|
||||
String fileName = userCode + "-" + dateString + ".csv";
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void createFile(PermissionAssistant assistant) {
|
||||
|
||||
File permFile = new File(Files.createTempDir(), getFileName(assistant));
|
||||
|
||||
try {
|
||||
|
||||
try (FileWriterWithEncoding fileWriter =
|
||||
new FileWriterWithEncoding(permFile, StandardCharsets.UTF_8)) {
|
||||
CSVWriter csvWriter = new CSVWriter(fileWriter, ';');
|
||||
writeGroup(csvWriter, assistant);
|
||||
}
|
||||
|
||||
createMetaFile(permFile, assistant);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getLocalizedMessage());
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void createMetaFile(File permFile, PermissionAssistant assistant) throws IOException {
|
||||
|
||||
assistant.setMetaFile(metaFiles.upload(permFile));
|
||||
|
||||
Beans.get(PermissionAssistantRepository.class).save(assistant);
|
||||
}
|
||||
|
||||
private Collection<String> getTranslatedStrings(
|
||||
Collection<String> strings, ResourceBundle bundle) {
|
||||
return strings.stream().map(bundle::getString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void writeGroup(CSVWriter csvWriter, PermissionAssistant assistant) {
|
||||
|
||||
String[] groupRow = null;
|
||||
Integer count = header.size();
|
||||
ResourceBundle bundle = I18n.getBundle(new Locale(assistant.getLanguage()));
|
||||
|
||||
List<String> headerRow = new ArrayList<String>();
|
||||
headerRow.addAll(getTranslatedStrings(header, bundle));
|
||||
if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_GROUPS) {
|
||||
groupRow = new String[header.size() + (assistant.getGroupSet().size() * groupHeader.size())];
|
||||
for (Group group : assistant.getGroupSet()) {
|
||||
groupRow[count + 1] = group.getCode();
|
||||
headerRow.addAll(getTranslatedStrings(groupHeader, bundle));
|
||||
count += groupHeader.size();
|
||||
}
|
||||
} else if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_ROLES) {
|
||||
groupRow = new String[header.size() + (assistant.getRoleSet().size() * groupHeader.size())];
|
||||
for (Role role : assistant.getRoleSet()) {
|
||||
groupRow[count + 1] = role.getName();
|
||||
headerRow.addAll(getTranslatedStrings(groupHeader, bundle));
|
||||
count += groupHeader.size();
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Header row created: {}", headerRow);
|
||||
|
||||
csvWriter.writeNext(groupRow);
|
||||
csvWriter.writeNext(headerRow.toArray(groupRow));
|
||||
|
||||
writeObject(csvWriter, assistant, groupRow.length, bundle);
|
||||
}
|
||||
|
||||
public Comparator<Object> compareField() {
|
||||
|
||||
return new Comparator<Object>() {
|
||||
@Override
|
||||
public int compare(Object field1, Object field2) {
|
||||
return ((MetaField) field1).getName().compareTo(((MetaField) field2).getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void writeObject(
|
||||
CSVWriter csvWriter, PermissionAssistant assistant, Integer size, ResourceBundle bundle) {
|
||||
|
||||
MetaField userField = assistant.getMetaField();
|
||||
|
||||
for (MetaModel object : assistant.getObjectSet()) {
|
||||
|
||||
int colIndex = header.size() + 1;
|
||||
String[] row = new String[size];
|
||||
row[0] = object.getFullName();
|
||||
|
||||
if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_GROUPS) {
|
||||
for (Group group : assistant.getGroupSet()) {
|
||||
String permName = getPermissionName(userField, object.getName(), group.getCode());
|
||||
colIndex = writePermission(object, userField, row, colIndex, permName);
|
||||
colIndex++;
|
||||
}
|
||||
|
||||
} else if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_ROLES) {
|
||||
for (Role role : assistant.getRoleSet()) {
|
||||
String permName = getPermissionName(userField, object.getName(), role.getName());
|
||||
colIndex = writePermission(object, userField, row, colIndex, permName);
|
||||
colIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
csvWriter.writeNext(row);
|
||||
|
||||
if (!assistant.getFieldPermission()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<MetaField> fieldList = object.getMetaFields();
|
||||
Collections.sort(fieldList, compareField());
|
||||
|
||||
for (MetaField field : fieldList) {
|
||||
colIndex = header.size() + 1;
|
||||
row = new String[size];
|
||||
row[1] = field.getName();
|
||||
row[2] = getFieldTitle(field, bundle);
|
||||
|
||||
if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_GROUPS) {
|
||||
for (Group group : assistant.getGroupSet()) {
|
||||
String permName = getPermissionName(null, object.getName(), group.getCode());
|
||||
colIndex = writeFieldPermission(field, row, colIndex, permName);
|
||||
colIndex++;
|
||||
}
|
||||
|
||||
} else if (assistant.getTypeSelect() == PermissionAssistantRepository.TYPE_ROLES) {
|
||||
for (Role role : assistant.getRoleSet()) {
|
||||
String permName = getPermissionName(null, object.getName(), role.getName());
|
||||
colIndex = writeFieldPermission(field, row, colIndex, permName);
|
||||
colIndex++;
|
||||
}
|
||||
}
|
||||
csvWriter.writeNext(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getPermissionName(MetaField userField, String objectName, String suffix) {
|
||||
|
||||
String permName = "perm." + objectName + "." + suffix;
|
||||
if (userField != null) {
|
||||
permName += "." + userField.getName();
|
||||
}
|
||||
|
||||
return permName;
|
||||
}
|
||||
|
||||
private String getFieldTitle(MetaField field, ResourceBundle bundle) {
|
||||
|
||||
String title = field.getLabel();
|
||||
if (!Strings.isNullOrEmpty(title)) {
|
||||
title = bundle.getString(title);
|
||||
if (Strings.isNullOrEmpty(title)) {
|
||||
title = field.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private int writeFieldPermission(MetaField field, String[] row, int colIndex, String permName) {
|
||||
|
||||
MetaPermissionRule rule =
|
||||
ruleRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.metaPermission.name = ?1 and self.metaPermission.object = ?2 and self.field = ?3",
|
||||
permName,
|
||||
field.getMetaModel().getFullName(),
|
||||
field.getName())
|
||||
.fetchOne();
|
||||
if (rule != null) {
|
||||
row[colIndex++] = rule.getCanRead() == false ? "" : "x";
|
||||
row[colIndex++] = rule.getCanWrite() == false ? "" : "x";
|
||||
row[colIndex++] = "";
|
||||
row[colIndex++] = "";
|
||||
row[colIndex++] = rule.getCanExport() == false ? "" : "x";
|
||||
row[colIndex++] = "";
|
||||
row[colIndex++] = "";
|
||||
row[colIndex++] =
|
||||
Strings.isNullOrEmpty(rule.getReadonlyIf()) ? "" : rule.getReadonlyIf(); // readonly if
|
||||
row[colIndex++] = Strings.isNullOrEmpty(rule.getHideIf()) ? "" : rule.getHideIf(); // hide if
|
||||
}
|
||||
|
||||
return colIndex;
|
||||
}
|
||||
|
||||
private int writePermission(
|
||||
MetaModel object, MetaField userField, String[] row, int colIndex, String permName) {
|
||||
|
||||
Permission perm = permissionRepository.findByName(permName);
|
||||
|
||||
if (perm != null && perm.getObject().equals(object.getFullName())) {
|
||||
row[colIndex++] = perm.getCanRead() == false ? "" : "x";
|
||||
row[colIndex++] = perm.getCanWrite() == false ? "" : "x";
|
||||
row[colIndex++] = perm.getCanCreate() == false ? "" : "x";
|
||||
row[colIndex++] = perm.getCanRemove() == false ? "" : "x";
|
||||
row[colIndex++] = perm.getCanExport() == false ? "" : "x";
|
||||
row[colIndex++] = Strings.isNullOrEmpty(perm.getCondition()) ? "" : perm.getCondition();
|
||||
row[colIndex++] =
|
||||
Strings.isNullOrEmpty(perm.getConditionParams()) ? "" : perm.getConditionParams();
|
||||
row[colIndex++] = ""; // readonly if
|
||||
row[colIndex++] = ""; // hide if
|
||||
} else if (userField != null) {
|
||||
MetaField objectField =
|
||||
fieldRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.typeName = ?1 and self.metaModel = ?2 and self.relationship = 'ManyToOne'",
|
||||
userField.getTypeName(),
|
||||
object)
|
||||
.fetchOne();
|
||||
if (objectField != null) {
|
||||
String condition = "";
|
||||
String conditionParams = "__user__." + userField.getName();
|
||||
|
||||
if (userField.getRelationship().contentEquals("ManyToOne")) {
|
||||
condition = "self." + objectField.getName() + " = ?";
|
||||
} else {
|
||||
condition = "self." + objectField.getName() + " in (?)";
|
||||
}
|
||||
row[colIndex++] = "x";
|
||||
row[colIndex++] = "x";
|
||||
row[colIndex++] = "x";
|
||||
row[colIndex++] = "x";
|
||||
row[colIndex++] = "x";
|
||||
row[colIndex++] = condition;
|
||||
row[colIndex++] = conditionParams;
|
||||
row[colIndex++] = ""; // readonly if
|
||||
row[colIndex++] = ""; // hide if
|
||||
}
|
||||
}
|
||||
|
||||
return colIndex;
|
||||
}
|
||||
|
||||
private static boolean headerEquals(
|
||||
Collection<String> standardRow,
|
||||
Collection<String> translatedRow,
|
||||
Collection<String> headerRow) {
|
||||
|
||||
if (standardRow.size() != translatedRow.size() || headerRow.size() != standardRow.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Iterator<String> itStandard = standardRow.iterator();
|
||||
Iterator<String> itTranslated = translatedRow.iterator();
|
||||
Iterator<String> it = headerRow.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
String standard = itStandard.next();
|
||||
String translated = itTranslated.next();
|
||||
String name = it.next();
|
||||
|
||||
if (!StringTool.equalsIgnoreCaseAndAccents(standard, name)
|
||||
&& !StringTool.equalsIgnoreCaseAndAccents(translated, name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkHeaderRow(Collection<String> headerRow, ResourceBundle bundle) {
|
||||
Collection<String> translatedHeader = getTranslatedStrings(header, bundle);
|
||||
Collection<String> translatedGroupHeader = getTranslatedStrings(groupHeader, bundle);
|
||||
|
||||
// Untranslated
|
||||
Collection<String> standardRow = Lists.newArrayList(header);
|
||||
for (int count = header.size(); count < headerRow.size(); count += groupHeader.size()) {
|
||||
standardRow.addAll(groupHeader);
|
||||
}
|
||||
|
||||
// Translated
|
||||
Collection<String> translatedRow = Lists.newArrayList(translatedHeader);
|
||||
for (int count = header.size(); count < headerRow.size(); count += groupHeader.size()) {
|
||||
translatedRow.addAll(translatedGroupHeader);
|
||||
}
|
||||
|
||||
LOG.debug("Standard Headers: {}", standardRow);
|
||||
LOG.debug("File Headers: {}", headerRow);
|
||||
|
||||
return headerEquals(standardRow, translatedRow, headerRow);
|
||||
}
|
||||
|
||||
public String importPermissions(PermissionAssistant permissionAssistant) {
|
||||
|
||||
try {
|
||||
ResourceBundle bundle = I18n.getBundle(new Locale(permissionAssistant.getLanguage()));
|
||||
MetaFile metaFile = permissionAssistant.getMetaFile();
|
||||
File csvFile = MetaFiles.getPath(metaFile).toFile();
|
||||
|
||||
try (CSVReader csvReader =
|
||||
new CSVReader(
|
||||
new InputStreamReader(new FileInputStream(csvFile), StandardCharsets.UTF_8), ';')) {
|
||||
|
||||
String[] groupRow = csvReader.readNext();
|
||||
if (groupRow == null || groupRow.length < 11) {
|
||||
errorLog = I18n.get(IMessage.BAD_FILE);
|
||||
}
|
||||
|
||||
String[] headerRow = csvReader.readNext();
|
||||
if (headerRow == null) {
|
||||
errorLog = I18n.get(IMessage.NO_HEADER);
|
||||
}
|
||||
if (!checkHeaderRow(Arrays.asList(headerRow), bundle)) {
|
||||
errorLog = I18n.get(IMessage.BAD_HEADER) + " " + Arrays.asList(headerRow);
|
||||
}
|
||||
|
||||
if (!errorLog.equals("")) {
|
||||
return errorLog;
|
||||
}
|
||||
|
||||
if (permissionAssistant.getTypeSelect() == PermissionAssistantRepository.TYPE_GROUPS) {
|
||||
Map<String, Group> groupMap = checkBadGroups(groupRow);
|
||||
processGroupCSV(
|
||||
csvReader,
|
||||
groupRow,
|
||||
groupMap,
|
||||
permissionAssistant.getMetaField(),
|
||||
permissionAssistant.getFieldPermission());
|
||||
saveGroups(groupMap);
|
||||
} else if (permissionAssistant.getTypeSelect()
|
||||
== PermissionAssistantRepository.TYPE_ROLES) {
|
||||
Map<String, Role> roleMap = checkBadRoles(groupRow);
|
||||
processRoleCSV(
|
||||
csvReader,
|
||||
groupRow,
|
||||
roleMap,
|
||||
permissionAssistant.getMetaField(),
|
||||
permissionAssistant.getFieldPermission());
|
||||
saveRoles(roleMap);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getLocalizedMessage());
|
||||
TraceBackService.trace(e);
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.ERR_IMPORT_WITH_MSG), e.getMessage());
|
||||
}
|
||||
|
||||
return errorLog;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveGroups(Map<String, Group> groupMap) {
|
||||
|
||||
for (Group group : groupMap.values()) {
|
||||
groupRepository.save(group);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveRoles(Map<String, Role> roleMap) {
|
||||
|
||||
for (Role role : roleMap.values()) {
|
||||
roleRepo.save(role);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Group> checkBadGroups(String[] groupRow) {
|
||||
|
||||
List<String> badGroups = new ArrayList<String>();
|
||||
Map<String, Group> groupMap = new HashMap<String, Group>();
|
||||
|
||||
for (Integer glen = header.size() + 1; glen < groupRow.length; glen += groupHeader.size()) {
|
||||
|
||||
String groupName = groupRow[glen];
|
||||
Group group = groupRepository.all().filter("self.code = ?1", groupName).fetchOne();
|
||||
if (group == null) {
|
||||
badGroups.add(groupName);
|
||||
} else {
|
||||
groupMap.put(groupName, group);
|
||||
}
|
||||
}
|
||||
|
||||
if (!badGroups.isEmpty()) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_GROUP), badGroups);
|
||||
}
|
||||
|
||||
return groupMap;
|
||||
}
|
||||
|
||||
private Map<String, Role> checkBadRoles(String[] roleRow) {
|
||||
|
||||
List<String> badroles = new ArrayList<String>();
|
||||
Map<String, Role> roleMap = new HashMap<String, Role>();
|
||||
|
||||
for (Integer len = header.size() + 1; len < roleRow.length; len += groupHeader.size()) {
|
||||
|
||||
String roleName = roleRow[len];
|
||||
Role role = roleRepo.all().filter("self.name = ?1", roleName).fetchOne();
|
||||
if (roleName == null) {
|
||||
badroles.add(roleName);
|
||||
} else {
|
||||
roleMap.put(roleName, role);
|
||||
}
|
||||
}
|
||||
|
||||
if (!badroles.isEmpty()) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_ROLE), badroles);
|
||||
}
|
||||
|
||||
return roleMap;
|
||||
}
|
||||
|
||||
private String checkObject(String objectName) {
|
||||
|
||||
MetaModel model = modelRepository.all().filter("self.fullName = ?1", objectName).fetchOne();
|
||||
|
||||
if (model == null) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_OBJECT), objectName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return objectName;
|
||||
}
|
||||
|
||||
private void processGroupCSV(
|
||||
CSVReader csvReader,
|
||||
String[] groupRow,
|
||||
Map<String, Group> groupMap,
|
||||
MetaField field,
|
||||
Boolean fieldBoolean)
|
||||
throws IOException {
|
||||
|
||||
Map<String, MetaPermission> metaPermDict = new HashMap<String, MetaPermission>();
|
||||
String objectName = null;
|
||||
|
||||
String[] row = csvReader.readNext();
|
||||
while (row != null) {
|
||||
for (Integer groupIndex = header.size() + 1;
|
||||
groupIndex < row.length;
|
||||
groupIndex += groupHeader.size()) {
|
||||
|
||||
String groupName = groupRow[groupIndex];
|
||||
if (!groupMap.containsKey(groupName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] rowGroup = Arrays.copyOfRange(row, groupIndex, groupIndex + groupHeader.size());
|
||||
|
||||
if (!Strings.isNullOrEmpty(groupName) && !Strings.isNullOrEmpty(row[0])) {
|
||||
objectName = checkObject(row[0]);
|
||||
if (objectName == null) {
|
||||
break;
|
||||
}
|
||||
if (fieldBoolean) {
|
||||
metaPermDict.put(groupName, getMetaPermission(groupMap.get(groupName), objectName));
|
||||
}
|
||||
updatePermission(groupMap.get(groupName), objectName, field, rowGroup);
|
||||
} else if (fieldBoolean && objectName != null && !Strings.isNullOrEmpty(row[1])) {
|
||||
updateFieldPermission(metaPermDict.get(groupName), row[1], rowGroup);
|
||||
}
|
||||
}
|
||||
|
||||
row = csvReader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void processRoleCSV(
|
||||
CSVReader csvReader,
|
||||
String[] roleRow,
|
||||
Map<String, Role> roleMap,
|
||||
MetaField field,
|
||||
Boolean fieldPermission)
|
||||
throws IOException {
|
||||
|
||||
Map<String, MetaPermission> metaPermDict = new HashMap<String, MetaPermission>();
|
||||
String objectName = null;
|
||||
|
||||
String[] row = csvReader.readNext();
|
||||
while (row != null) {
|
||||
|
||||
for (Integer groupIndex = header.size() + 1;
|
||||
groupIndex < row.length;
|
||||
groupIndex += groupHeader.size()) {
|
||||
|
||||
String roleName = roleRow[groupIndex];
|
||||
if (!roleMap.containsKey(roleName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] rowGroup = Arrays.copyOfRange(row, groupIndex, groupIndex + groupHeader.size());
|
||||
|
||||
if (!Strings.isNullOrEmpty(roleName) && !Strings.isNullOrEmpty(row[0])) {
|
||||
objectName = checkObject(row[0]);
|
||||
if (objectName == null) {
|
||||
break;
|
||||
}
|
||||
if (fieldPermission) {
|
||||
metaPermDict.put(roleName, getMetaPermission(roleMap.get(roleName), objectName));
|
||||
}
|
||||
updatePermission(roleMap.get(roleName), objectName, field, rowGroup);
|
||||
} else if (fieldPermission && objectName != null && !Strings.isNullOrEmpty(row[1])) {
|
||||
updateFieldPermission(metaPermDict.get(roleName), row[1], rowGroup);
|
||||
}
|
||||
}
|
||||
|
||||
row = csvReader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
public MetaPermission getMetaPermission(Group group, String objectName) {
|
||||
|
||||
String[] objectNames = objectName.split("\\.");
|
||||
String groupName = group.getCode();
|
||||
String permName = getPermissionName(null, objectNames[objectNames.length - 1], group.getCode());
|
||||
MetaPermission metaPermission =
|
||||
metaPermissionRepository.all().filter("self.name = ?1", permName).fetchOne();
|
||||
|
||||
if (metaPermission == null) {
|
||||
LOG.debug("Create metaPermission group: {}, object: {}", groupName, objectName);
|
||||
|
||||
metaPermission = new MetaPermission();
|
||||
metaPermission.setName(permName);
|
||||
metaPermission.setObject(objectName);
|
||||
|
||||
group.addMetaPermission(metaPermission);
|
||||
}
|
||||
|
||||
return metaPermission;
|
||||
}
|
||||
|
||||
public MetaPermission getMetaPermission(Role role, String objectName) {
|
||||
|
||||
String[] objectNames = objectName.split("\\.");
|
||||
String roleName = role.getName();
|
||||
String permName = getPermissionName(null, objectNames[objectNames.length - 1], roleName);
|
||||
MetaPermission metaPermission =
|
||||
metaPermissionRepository.all().filter("self.name = ?1", permName).fetchOne();
|
||||
|
||||
if (metaPermission == null) {
|
||||
LOG.debug("Create metaPermission role: {}, object: {}", roleName, objectName);
|
||||
|
||||
metaPermission = new MetaPermission();
|
||||
metaPermission.setName(permName);
|
||||
metaPermission.setObject(objectName);
|
||||
|
||||
role.addMetaPermission(metaPermission);
|
||||
}
|
||||
|
||||
return metaPermission;
|
||||
}
|
||||
|
||||
public MetaPermission updateFieldPermission(
|
||||
MetaPermission metaPermission, String field, String[] row) {
|
||||
|
||||
MetaPermissionRule permissionRule =
|
||||
ruleRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.field = ?1 and self.metaPermission.name = ?2",
|
||||
field,
|
||||
metaPermission.getName())
|
||||
.fetchOne();
|
||||
|
||||
if (permissionRule == null) {
|
||||
permissionRule = new MetaPermissionRule();
|
||||
permissionRule.setMetaPermission(metaPermission);
|
||||
permissionRule.setField(field);
|
||||
}
|
||||
|
||||
permissionRule.setCanRead(row[0].equalsIgnoreCase("x"));
|
||||
permissionRule.setCanWrite(row[1].equalsIgnoreCase("x"));
|
||||
permissionRule.setCanExport(row[4].equalsIgnoreCase("x"));
|
||||
permissionRule.setReadonlyIf(row[5]);
|
||||
permissionRule.setHideIf(row[6]);
|
||||
metaPermission.addRule(permissionRule);
|
||||
|
||||
return metaPermission;
|
||||
}
|
||||
|
||||
public void updatePermission(Group group, String objectName, MetaField field, String[] row) {
|
||||
|
||||
String[] objectNames = objectName.split("\\.");
|
||||
String permName =
|
||||
getPermissionName(field, objectNames[objectNames.length - 1], group.getCode());
|
||||
|
||||
Permission permission =
|
||||
permissionRepository.all().filter("self.name = ?1", permName).fetchOne();
|
||||
boolean newPermission = false;
|
||||
|
||||
if (permission == null) {
|
||||
newPermission = true;
|
||||
permission = new Permission();
|
||||
permission.setName(permName);
|
||||
permission.setObject(objectName);
|
||||
}
|
||||
|
||||
permission.setCanRead(row[0].equalsIgnoreCase("x"));
|
||||
permission.setCanWrite(row[1].equalsIgnoreCase("x"));
|
||||
permission.setCanCreate(row[2].equalsIgnoreCase("x"));
|
||||
permission.setCanRemove(row[3].equalsIgnoreCase("x"));
|
||||
permission.setCanExport(row[4].equalsIgnoreCase("x"));
|
||||
|
||||
if (newPermission) {
|
||||
group.addPermission(permission);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePermission(Role role, String objectName, MetaField field, String[] row) {
|
||||
|
||||
String[] objectNames = objectName.split("\\.");
|
||||
String permName = getPermissionName(field, objectNames[objectNames.length - 1], role.getName());
|
||||
|
||||
Permission permission =
|
||||
permissionRepository.all().filter("self.name = ?1", permName).fetchOne();
|
||||
boolean newPermission = false;
|
||||
|
||||
if (permission == null) {
|
||||
newPermission = true;
|
||||
permission = new Permission();
|
||||
permission.setName(permName);
|
||||
permission.setObject(objectName);
|
||||
}
|
||||
|
||||
permission.setCanRead(row[0].equalsIgnoreCase("x"));
|
||||
permission.setCanWrite(row[1].equalsIgnoreCase("x"));
|
||||
permission.setCanCreate(row[2].equalsIgnoreCase("x"));
|
||||
permission.setCanRemove(row[3].equalsIgnoreCase("x"));
|
||||
permission.setCanExport(row[4].equalsIgnoreCase("x"));
|
||||
permission.setCondition(row[5]);
|
||||
permission.setConditionParams(row[6]);
|
||||
|
||||
if (newPermission) {
|
||||
role.addPermission(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.auth.web;
|
||||
|
||||
import com.axelor.auth.db.IMessage;
|
||||
import com.axelor.auth.db.PermissionAssistant;
|
||||
import com.axelor.auth.db.repo.PermissionAssistantRepository;
|
||||
import com.axelor.auth.service.PermissionAssistantService;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.MetaField;
|
||||
import com.axelor.meta.db.MetaModel;
|
||||
import com.axelor.meta.db.repo.MetaModelRepository;
|
||||
import com.axelor.rpc.ActionRequest;
|
||||
import com.axelor.rpc.ActionResponse;
|
||||
import com.google.inject.Singleton;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class PermissionAssistantController {
|
||||
|
||||
public void createFile(ActionRequest request, ActionResponse response) {
|
||||
try {
|
||||
Long permissionAssistantId = (Long) request.getContext().get("id");
|
||||
Beans.get(PermissionAssistantService.class)
|
||||
.createFile(Beans.get(PermissionAssistantRepository.class).find(permissionAssistantId));
|
||||
response.setReload(true);
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(response, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void importPermissions(ActionRequest request, ActionResponse response) {
|
||||
try {
|
||||
Long permissionAssistantId = (Long) request.getContext().get("id");
|
||||
String errors =
|
||||
Beans.get(PermissionAssistantService.class)
|
||||
.importPermissions(
|
||||
Beans.get(PermissionAssistantRepository.class).find(permissionAssistantId));
|
||||
response.setValue("importDate", LocalDateTime.now());
|
||||
response.setValue("log", errors);
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
response.setFlash(I18n.get(IMessage.IMPORT_OK));
|
||||
} else {
|
||||
response.setFlash(I18n.get(IMessage.ERR_IMPORT));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(response, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void fillObjects(ActionRequest request, ActionResponse response) {
|
||||
try {
|
||||
PermissionAssistant assistant = request.getContext().asType(PermissionAssistant.class);
|
||||
MetaField metaField = assistant.getMetaField();
|
||||
|
||||
if (metaField != null
|
||||
&& (assistant.getObjectSet() == null || assistant.getObjectSet().isEmpty())) {
|
||||
|
||||
List<MetaModel> models =
|
||||
Beans.get(MetaModelRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.metaFields.relationship = 'ManyToOne'"
|
||||
+ " and self.metaFields.typeName = ?1",
|
||||
metaField.getTypeName())
|
||||
.fetch();
|
||||
|
||||
Set<MetaModel> objectSet = new HashSet<>();
|
||||
objectSet.addAll(models);
|
||||
response.setValue("objectSet", objectSet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(response, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.csv.script;
|
||||
|
||||
import com.axelor.app.AppSettings;
|
||||
import com.axelor.apps.base.db.App;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ImportApp {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ImportApp.class);
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
public Object importApp(Object bean, Map<String, Object> values) {
|
||||
|
||||
assert bean instanceof App;
|
||||
|
||||
App app = (App) bean;
|
||||
|
||||
final Path path = (Path) values.get("__path__");
|
||||
String fileName = (String) values.get("imagePath");
|
||||
|
||||
if (!Strings.isNullOrEmpty(fileName)) {
|
||||
try {
|
||||
final File image = path.resolve("img" + File.separator + fileName).toFile();
|
||||
if (image.exists()) {
|
||||
final MetaFile metaFile = metaFiles.upload(image);
|
||||
app.setImage(metaFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Can't load image {} for app {}", fileName, app.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (app.getLanguageSelect() == null) {
|
||||
String language = AppSettings.get().get("application.locale");
|
||||
app.setLanguageSelect(language);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.meta.service;
|
||||
|
||||
import com.axelor.auth.db.Group;
|
||||
import com.axelor.auth.db.IMessage;
|
||||
import com.axelor.auth.db.Role;
|
||||
import com.axelor.auth.db.repo.GroupRepository;
|
||||
import com.axelor.auth.db.repo.RoleRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.axelor.meta.db.MetaGroupMenuAssistant;
|
||||
import com.axelor.meta.db.MetaMenu;
|
||||
import com.axelor.meta.db.repo.MetaGroupMenuAssistantRepository;
|
||||
import com.axelor.meta.db.repo.MetaMenuRepository;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.opencsv.CSVReader;
|
||||
import com.opencsv.CSVWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.io.output.FileWriterWithEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MetaGroupMenuAssistantService {
|
||||
|
||||
@Inject private MetaGroupMenuAssistantRepository menuAssistantRepository;
|
||||
|
||||
@Inject private GroupRepository groupRepository;
|
||||
|
||||
@Inject private MetaMenuRepository menuRepository;
|
||||
|
||||
@Inject private RoleRepository roleRepository;
|
||||
|
||||
@Inject private MetaFiles metaFiles;
|
||||
|
||||
private List<String> badGroups = new ArrayList<>();
|
||||
|
||||
private List<String> badRoles = new ArrayList<>();
|
||||
|
||||
private String errorLog = "";
|
||||
|
||||
private Set<MetaMenu> updatedMenus = Sets.newHashSet();
|
||||
|
||||
private ResourceBundle bundle;
|
||||
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private String getFileName(MetaGroupMenuAssistant groupMenuAssistant) {
|
||||
|
||||
String userCode = groupMenuAssistant.getCreatedBy().getCode();
|
||||
String dateString = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
|
||||
String fileName = "GroupMenu" + "-" + userCode + "-" + dateString + ".csv";
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void setBundle(Locale locale) {
|
||||
bundle = I18n.getBundle(locale);
|
||||
}
|
||||
|
||||
private ResourceBundle getBundle() {
|
||||
if (bundle == null) {
|
||||
bundle = I18n.getBundle(new Locale("en"));
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void createGroupMenuFile(MetaGroupMenuAssistant groupMenuAssistant) throws IOException {
|
||||
|
||||
setBundle(new Locale(groupMenuAssistant.getLanguage()));
|
||||
File groupMenuFile = MetaFiles.createTempFile("MenuGroup", ".csv").toFile();
|
||||
|
||||
try {
|
||||
|
||||
List<String[]> rows = createHeader(groupMenuAssistant);
|
||||
|
||||
addMenuRows(groupMenuAssistant, rows);
|
||||
|
||||
addGroupAccess(rows);
|
||||
|
||||
CSVWriter csvWriter = new CSVWriter(new FileWriterWithEncoding(groupMenuFile, "utf-8"), ';');
|
||||
csvWriter.writeAll(rows);
|
||||
csvWriter.close();
|
||||
|
||||
groupMenuAssistant.setMetaFile(
|
||||
metaFiles.upload(new FileInputStream(groupMenuFile), getFileName(groupMenuAssistant)));
|
||||
menuAssistantRepository.save(groupMenuAssistant);
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String[]> createHeader(MetaGroupMenuAssistant groupMenuAssistant)
|
||||
throws IOException {
|
||||
|
||||
MetaFile metaFile = groupMenuAssistant.getMetaFile();
|
||||
List<String[]> rows = new ArrayList<String[]>();
|
||||
if (metaFile != null) {
|
||||
File csvFile = MetaFiles.getPath(metaFile).toFile();
|
||||
logger.debug("File name: {}", csvFile.getAbsolutePath());
|
||||
logger.debug("File length: {}", csvFile.length());
|
||||
try (CSVReader csvReader =
|
||||
new CSVReader(
|
||||
new InputStreamReader(new FileInputStream(csvFile), StandardCharsets.UTF_8), ';')) {
|
||||
rows = csvReader.readAll();
|
||||
logger.debug("Rows size: {}", rows.size());
|
||||
}
|
||||
}
|
||||
if (!rows.isEmpty()) {
|
||||
rows.set(0, getGroupRow(rows.get(0), groupMenuAssistant));
|
||||
} else {
|
||||
rows.add(getGroupRow(null, groupMenuAssistant));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private String[] getGroupRow(String[] row, MetaGroupMenuAssistant groupMenuAssistant)
|
||||
throws IOException {
|
||||
|
||||
List<String> groupList = new ArrayList<String>();
|
||||
if (row != null) {
|
||||
groupList.addAll(Arrays.asList(row));
|
||||
} else {
|
||||
groupList.add(getBundle().getString("Name"));
|
||||
groupList.add(getBundle().getString("Title"));
|
||||
}
|
||||
|
||||
for (Group group : groupMenuAssistant.getGroupSet()) {
|
||||
String code = group.getCode();
|
||||
if (!groupList.contains(code)) {
|
||||
groupList.add(code);
|
||||
}
|
||||
}
|
||||
|
||||
for (Role role : groupMenuAssistant.getRoleSet()) {
|
||||
String name = role.getName();
|
||||
if (!groupList.contains(name)) {
|
||||
groupList.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return groupList.toArray(new String[groupList.size()]);
|
||||
}
|
||||
|
||||
private void addMenuRows(MetaGroupMenuAssistant groupMenuAssistant, List<String[]> rows) {
|
||||
String[] groupRow = rows.get(0);
|
||||
rows.remove(0);
|
||||
Set<String> names = new HashSet<>();
|
||||
|
||||
for (String[] line : rows) {
|
||||
names.add(line[0]);
|
||||
}
|
||||
|
||||
for (MetaMenu metaMenu : groupMenuAssistant.getMenuSet()) {
|
||||
|
||||
String name = metaMenu.getName();
|
||||
|
||||
if (names.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String title = metaMenu.getTitle();
|
||||
String translation = getBundle().getString(title);
|
||||
|
||||
if (!Strings.isNullOrEmpty(translation)) {
|
||||
title = translation;
|
||||
}
|
||||
|
||||
String[] menu = new String[groupRow.length];
|
||||
menu[0] = name;
|
||||
menu[1] = title;
|
||||
rows.add(menu);
|
||||
}
|
||||
|
||||
Collections.sort(
|
||||
rows,
|
||||
new Comparator<String[]>() {
|
||||
|
||||
@Override
|
||||
public int compare(String[] first, String[] second) {
|
||||
return first[0].compareTo(second[0]);
|
||||
}
|
||||
});
|
||||
|
||||
rows.add(0, groupRow);
|
||||
}
|
||||
|
||||
private void addGroupAccess(List<String[]> rows) {
|
||||
|
||||
ListIterator<String[]> rowIter = rows.listIterator();
|
||||
|
||||
String[] header = rowIter.next();
|
||||
|
||||
while (rowIter.hasNext()) {
|
||||
String[] row = rowIter.next();
|
||||
MetaMenu menu =
|
||||
menuRepository.all().filter("self.name = ?1", row[0]).order("-priority").fetchOne();
|
||||
|
||||
if (row.length < header.length) {
|
||||
row = Arrays.copyOf(row, header.length);
|
||||
rowIter.set(row);
|
||||
}
|
||||
|
||||
for (int i = 2; i < header.length; i++) {
|
||||
for (Group group : menu.getGroups()) {
|
||||
if (header[i] != null && header[i].equals(group.getCode())) {
|
||||
row[i] = "x";
|
||||
}
|
||||
}
|
||||
for (Role role : menu.getRoles()) {
|
||||
if (header[i] != null && header[i].equals(role.getName())) {
|
||||
row[i] = "x";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> checkGroups(String[] groupRow) {
|
||||
|
||||
Map<String, Object> groupMap = new HashMap<String, Object>();
|
||||
|
||||
for (Integer glen = 2; glen < groupRow.length; glen++) {
|
||||
|
||||
Group group = groupRepository.all().filter("self.code = ?1", groupRow[glen]).fetchOne();
|
||||
|
||||
if (group == null) {
|
||||
badGroups.add(groupRow[glen]);
|
||||
} else {
|
||||
groupMap.put(groupRow[glen], group);
|
||||
}
|
||||
}
|
||||
|
||||
return groupMap;
|
||||
}
|
||||
|
||||
private Map<String, Role> checkRoles(String[] roleRow) {
|
||||
|
||||
Map<String, Role> roleMap = new HashMap<String, Role>();
|
||||
|
||||
for (Integer rlen = 2; rlen < roleRow.length; rlen++) {
|
||||
|
||||
Role role = roleRepository.all().filter("self.name = ?1", roleRow[rlen]).fetchOne();
|
||||
|
||||
if (role == null) {
|
||||
badRoles.add(roleRow[rlen]);
|
||||
} else {
|
||||
roleMap.put(roleRow[rlen], role);
|
||||
}
|
||||
}
|
||||
|
||||
return roleMap;
|
||||
}
|
||||
|
||||
public String importGroupMenu(MetaGroupMenuAssistant groupMenuAssistant) {
|
||||
|
||||
try {
|
||||
setBundle(new Locale(groupMenuAssistant.getLanguage()));
|
||||
MetaFile metaFile = groupMenuAssistant.getMetaFile();
|
||||
File csvFile = MetaFiles.getPath(metaFile).toFile();
|
||||
|
||||
CSVReader csvReader =
|
||||
new CSVReader(
|
||||
new InputStreamReader(new FileInputStream(csvFile), StandardCharsets.UTF_8), ';');
|
||||
|
||||
String[] groupRow = csvReader.readNext();
|
||||
if (groupRow == null || groupRow.length < 3) {
|
||||
csvReader.close();
|
||||
return I18n.get(IMessage.BAD_FILE);
|
||||
}
|
||||
|
||||
Map<String, Object> groupMap = checkGroups(groupRow);
|
||||
groupMap.putAll(checkRoles(groupRow));
|
||||
badGroups.removeAll(groupMap.keySet());
|
||||
badRoles.removeAll(groupMap.keySet());
|
||||
if (!badGroups.isEmpty()) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_GROUP), badGroups);
|
||||
}
|
||||
if (!badRoles.isEmpty()) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_ROLE), badRoles);
|
||||
}
|
||||
Group admin = groupRepository.findByCode("admins");
|
||||
|
||||
for (String[] row : csvReader.readAll()) {
|
||||
importMenus(row, groupRow, groupMap, admin);
|
||||
}
|
||||
|
||||
csvReader.close();
|
||||
|
||||
saveMenus();
|
||||
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(e);
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.ERR_IMPORT_WITH_MSG), e.getMessage());
|
||||
}
|
||||
|
||||
return errorLog;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveMenus() {
|
||||
|
||||
for (MetaMenu menu : updatedMenus) {
|
||||
menuRepository.save(menu);
|
||||
}
|
||||
}
|
||||
|
||||
private void importMenus(
|
||||
String[] row, String[] groupRow, Map<String, Object> groupMap, Group admin)
|
||||
throws IOException {
|
||||
|
||||
List<MetaMenu> menus =
|
||||
menuRepository.all().filter("self.name = ?1", row[0]).order("-priority").fetch();
|
||||
|
||||
if (menus.isEmpty()) {
|
||||
errorLog += "\n" + String.format(I18n.get(IMessage.NO_MENU), row[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (MetaMenu menu : menus) {
|
||||
boolean noAccess = true;
|
||||
|
||||
for (Integer mIndex = 2; mIndex < row.length; mIndex++) {
|
||||
|
||||
String code = groupRow[mIndex];
|
||||
Object object = groupMap.get(code);
|
||||
|
||||
Role role = null;
|
||||
Group group = null;
|
||||
if (object instanceof Group) {
|
||||
group = (Group) object;
|
||||
} else if (object instanceof Role) {
|
||||
role = (Role) object;
|
||||
}
|
||||
|
||||
if (row[mIndex].equalsIgnoreCase("x")) {
|
||||
noAccess = false;
|
||||
if (group != null) {
|
||||
menu.addGroup(group);
|
||||
}
|
||||
if (role != null) {
|
||||
menu.addRole(role);
|
||||
}
|
||||
updatedMenus.add(menu);
|
||||
} else if (group != null && menu.getGroups().contains(group)) {
|
||||
menu.removeGroup(group);
|
||||
updatedMenus.add(menu);
|
||||
} else if (role != null && menu.getRoles().contains(role)) {
|
||||
menu.removeRole(role);
|
||||
updatedMenus.add(menu);
|
||||
}
|
||||
}
|
||||
|
||||
if (noAccess && admin != null) {
|
||||
menu.addGroup(admin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.axelor.meta.web;
|
||||
|
||||
import com.axelor.auth.db.IMessage;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.repo.MetaGroupMenuAssistantRepository;
|
||||
import com.axelor.meta.service.MetaGroupMenuAssistantService;
|
||||
import com.axelor.rpc.ActionRequest;
|
||||
import com.axelor.rpc.ActionResponse;
|
||||
import com.google.inject.Singleton;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Singleton
|
||||
public class MetaGroupMenuAssistantController {
|
||||
|
||||
public void createGroupMenuFile(ActionRequest request, ActionResponse response) {
|
||||
try {
|
||||
Long groupMenuAssistantId = (Long) request.getContext().get("id");
|
||||
Beans.get(MetaGroupMenuAssistantService.class)
|
||||
.createGroupMenuFile(
|
||||
Beans.get(MetaGroupMenuAssistantRepository.class).find(groupMenuAssistantId));
|
||||
response.setReload(true);
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(response, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void importGroupMenu(ActionRequest request, ActionResponse response) {
|
||||
try {
|
||||
Long groupMenuAssistantId = (Long) request.getContext().get("id");
|
||||
String errorLog =
|
||||
Beans.get(MetaGroupMenuAssistantService.class)
|
||||
.importGroupMenu(
|
||||
Beans.get(MetaGroupMenuAssistantRepository.class).find(groupMenuAssistantId));
|
||||
|
||||
response.setValue("log", errorLog);
|
||||
if (errorLog.isEmpty()) {
|
||||
response.setFlash(I18n.get(IMessage.IMPORT_OK));
|
||||
response.setValue("importDate", LocalDateTime.now());
|
||||
} else {
|
||||
response.setFlash(I18n.get(IMessage.ERR_IMPORT));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(response, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<csv-inputs xmlns="http://axelor.com/xml/ns/data-import"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
|
||||
|
||||
<input file="auth_permission.csv" separator=";" type="com.axelor.auth.db.Permission" search="self.name = :name" call="com.axelor.csv.script.ImportPermission:importPermissionToRole">
|
||||
<bind to="canRead" eval="can_read == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canWrite" eval="can_write == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canCreate" eval="can_create == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canRemove" eval="can_remove == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canExport" eval="can_export == 'x' ? 'true' : 'false'"/>
|
||||
</input>
|
||||
|
||||
<input file="meta_metaMenu.csv" separator=";" type="com.axelor.meta.db.MetaMenu" search="self.name = :name and self.module = 'axelor-admin'" update="true" />
|
||||
|
||||
</csv-inputs>
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
"name";"object";"can_read";"can_write";"can_create";"can_remove";"can_export";"condition";"conditionParams";"roleName"
|
||||
"perm.auth.all";"com.axelor.auth.db.*";"x";"x";"x";"x";"x";;;"Admin"
|
||||
"perm.meta.all";"com.axelor.meta.db.*";"x";"x";"x";"x";"x";;;"Admin"
|
||||
"perm.mail.all";"com.axelor.mail.db.*";"x";"x";"x";"x";"x";;;"Admin"
|
||||
"perm.dms.all";"com.axelor.dms.db.*";"x";"x";"x";"x";"x";;;"Admin"
|
||||
"perm.team.all";"com.axelor.team.db.*";"x";"x";"x";"x";"x";;;"Admin"
|
||||
|
@ -0,0 +1,7 @@
|
||||
"name";"roles.name"
|
||||
"admin-root";"Admin"
|
||||
"admin-root-app-management";"Admin"
|
||||
"admin-root-maintenance";"Admin"
|
||||
"admin-root-object-data-config";"Admin"
|
||||
"menu-auth-permission-assistant";"Admin"
|
||||
"menu-meta-group-menu-assistant";"Admin"
|
||||
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="AccessConfig" cacheable="true">
|
||||
<string name="name" readonly="true"/>
|
||||
<many-to-one name="app" ref="com.axelor.apps.base.db.App" title="App" />
|
||||
<many-to-many name="roleSet" ref="com.axelor.auth.db.Role" title="Roles"/>
|
||||
<unique-constraint columns="app,name"/>
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="App" lang="java" strategy="JOINED" cacheable="true">
|
||||
|
||||
<string name="name" title="Name" required="true" translatable="true"/>
|
||||
<string name="code" title="Code" required="true" unique="true"/>
|
||||
<string name="description" title="Description" large="true" translatable="true"/>
|
||||
<string name="modules" title="Modules contains in the app" />
|
||||
<many-to-many name="dependsOnSet" title="Depends on" ref="App"/>
|
||||
<many-to-one name="image" ref="com.axelor.meta.db.MetaFile" title="Image"/>
|
||||
<boolean name="initDataLoaded" title="Init data loaded" />
|
||||
<boolean name="demoDataLoaded" title="Demo data loaded" />
|
||||
<boolean name="isRolesImported" title="Roles imported" />
|
||||
<boolean name="active" title="Installed" />
|
||||
<integer name="sequence" title="Sequence" />
|
||||
<integer name="installOrder" title="Install order" />
|
||||
<string name="languageSelect" title="Language" selection="select.language" />
|
||||
<one-to-many name="accessConfigList" ref="AccessConfig" title="Access config" mappedBy="app"/>
|
||||
|
||||
<finder-method name="findByCode" using="code" cacheable="true" />
|
||||
|
||||
<finder-method name="findByName" using="name" cacheable="true" />
|
||||
|
||||
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db" />
|
||||
|
||||
<entity name="DataBackup">
|
||||
<many-to-one name="backupMetaFile" ref="com.axelor.meta.db.MetaFile"
|
||||
title="Backup File" />
|
||||
<integer name="statusSelect" selection="data.backup.status.select"
|
||||
title="Status" default="0" />
|
||||
<many-to-one name="logMetaFile" ref="com.axelor.meta.db.MetaFile"
|
||||
title="Log" />
|
||||
<datetime name="backupDate" title="Backup On" />
|
||||
<datetime name="restoreDate" title="Restored On" />
|
||||
<integer name="fetchLimit" title="CSV Backup Fetch Limit"/>
|
||||
<boolean name="isRelativeDate" title="Relative Date"/>
|
||||
<boolean name="updateImportId" title="Update Import Id"/>
|
||||
<extra-code>
|
||||
<![CDATA[
|
||||
// DATABACKUP STATUS
|
||||
public static final int DATA_BACKUP_STATUS_DRAFT = 0;
|
||||
public static final int DATA_BACKUP_STATUS_IN_PROGRESS = 1;
|
||||
public static final int DATA_BACKUP_STATUS_CREATED = 2;
|
||||
public static final int DATA_BACKUP_STATUS_RESTORE = 3;
|
||||
public static final int DATA_BACKUP_STATUS_RESTORE_ERROR = 4;
|
||||
]]>
|
||||
</extra-code>
|
||||
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="DataConfigLine" cacheable="true">
|
||||
<![CDATA[
|
||||
This object store the configuration lines for export of data
|
||||
]]>
|
||||
<many-to-one name="metaModel" title="Object" ref="com.axelor.meta.db.MetaModel"/>
|
||||
<integer name="typeSelect" title="Query/path" selection="data.config.line.type.select" />
|
||||
<string name="path" title="Query" />
|
||||
<many-to-one name="metaFieldPath" ref="com.axelor.meta.db.MetaField" title="Path Field"/>
|
||||
<many-to-many name="toExportMetaFieldSet" ref="com.axelor.meta.db.MetaField" title="Fields to export" />
|
||||
<many-to-one name="objectDataConfig" ref="ObjectDataConfig" />
|
||||
<integer name="resetPathSelect" title="Action to perform" selection="data.config.line.reset.path.select" />
|
||||
<string name="record" title="Record" selection="object.data.config.model.select"/>
|
||||
<long name="recordSelectId" />
|
||||
<many-to-many name="toDeleteMetaFieldSet" ref="com.axelor.meta.db.MetaField" title="Fields to delete" />
|
||||
<string name="tabName" title="Tab name" required="true"/>
|
||||
<unique-constraint columns="objectDataConfig,tabName"/>
|
||||
<extra-code>
|
||||
<![CDATA[
|
||||
public static final int TYPE_PATH = 0;
|
||||
public static final int TYPE_QUERY = 1;
|
||||
|
||||
public static final int RESET_NONE = 0;
|
||||
public static final int RESET_REPLACE = 1;
|
||||
public static final int RESET_DELETE = 2;
|
||||
]]>
|
||||
</extra-code>
|
||||
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="meta" package="com.axelor.meta.db" />
|
||||
|
||||
<entity name="MetaGroupMenuAssistant" table="META_GROUP_MENU_ASSISTANT">
|
||||
<many-to-many name="groupSet" ref="com.axelor.auth.db.Group" title="Groups"/>
|
||||
<many-to-many name="roleSet" ref="com.axelor.auth.db.Role" title="Roles"/>
|
||||
<many-to-many name="menuSet" ref="MetaMenu" title="Menus"/>
|
||||
<many-to-one name="metaFile" ref="MetaFile" title="File" />
|
||||
<datetime name="importDate" title="Import date"/>
|
||||
<string name="language" title="Language" selection="select.language" default="en"/>
|
||||
<string name="log" large="true" title="Log" />
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="core" package="com.axelor.db" />
|
||||
|
||||
<entity name="Model">
|
||||
<string name="importId" copy="false" hashKey="false" readonly="true" title="Import ID" unique="true" />
|
||||
<string name="importOrigin" copy="false" hashKey="false" readonly="true" title="Imported from" />
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="ObjectDataConfig" cacheable="true">
|
||||
<![CDATA[
|
||||
This object store the configuration for export of data
|
||||
]]>
|
||||
<string name="title" title="Title" />
|
||||
<string name="modelSelect" namecolumn="true" title="Model" selection="object.data.config.model.select" required="true"/>
|
||||
<integer name="statusSelect" title="Status" selection="object.data.config.status.select" />
|
||||
<one-to-many name="dataConfigLineList" ref="DataConfigLine" title="Lines" mappedBy="objectDataConfig"/>
|
||||
<string name="comment" column="comment_val" title="Comment" large="true"/>
|
||||
|
||||
<extra-code>
|
||||
<![CDATA[
|
||||
|
||||
// STATUS SELECT
|
||||
public static final int STATUS_DRAFT = 1;
|
||||
public static final int STATUS_VALIDATED = 2;
|
||||
public static final int STATUS_CANCELLED = 3;
|
||||
|
||||
]]>
|
||||
</extra-code>
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="ObjectDataConfigExport" cacheable="true">
|
||||
<![CDATA[
|
||||
This object store the detail of export data
|
||||
]]>
|
||||
<string name="modelSelect" title="Model" selection="object.data.config.model.select" required="true"/>
|
||||
<long name="modelSelectId"/>
|
||||
<string name="recordName" title="Record name"/>
|
||||
<string name="exportFormatSelect" title="Export format" selection="object.data.export.format.select" />
|
||||
<string name="langSelect" title="Language" selection="select.language" />
|
||||
<many-to-one name="objectDataConfig" ref="com.axelor.apps.base.db.ObjectDataConfig" title="Object data config"/>
|
||||
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="auth" package="com.axelor.auth.db"/>
|
||||
|
||||
<entity name="PermissionAssistant" cacheable="true">
|
||||
<![CDATA[
|
||||
This object stores the permission assistant.
|
||||
]]>
|
||||
<datetime name="importDate" title="Import date" readonly="true" />
|
||||
<integer name="typeSelect" title="Type" required="true"/>
|
||||
<many-to-one name="metaField" title="User field" ref="com.axelor.meta.db.MetaField"/>
|
||||
<many-to-one name="metaFile" ref="com.axelor.meta.db.MetaFile" title="File" />
|
||||
<many-to-many name="roleSet" ref="Role" title="Roles"/>
|
||||
<many-to-many name="groupSet" ref="Group" title="Groups"/>
|
||||
<many-to-many name="objectSet" ref="com.axelor.meta.db.MetaModel" title="Objects"/>
|
||||
<string name="language" title="Language" selection="select.language" default="en"/>
|
||||
<string name="log" large="true" title="Log" />
|
||||
<boolean name="fieldPermission" title="Field permission"/>
|
||||
|
||||
<extra-code>
|
||||
<![CDATA[
|
||||
public static final int TYPE_GROUPS = 1;
|
||||
public static final int TYPE_ROLES = 2;
|
||||
]]>
|
||||
</extra-code>
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" ?>
|
||||
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
|
||||
|
||||
<module name="base" package="com.axelor.apps.base.db"/>
|
||||
|
||||
<entity name="UserAccessConfig" cacheable="true">
|
||||
<many-to-one name="user" ref="com.axelor.auth.db.User" title="User" column="user_id"/>
|
||||
<many-to-one name="app" ref="com.axelor.apps.base.db.App" title="App" />
|
||||
<many-to-one name="accessConfig" ref="com.axelor.apps.base.db.AccessConfig" title="Access config" />
|
||||
</entity>
|
||||
|
||||
</domain-models>
|
||||
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config",,,
|
||||
"Access config imported successfully",,,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data",,,
|
||||
"App",,,
|
||||
"Application Config",,,
|
||||
"Apps",,,
|
||||
"Apps installed successfully",,,
|
||||
"Apps management",,,
|
||||
"Apps refreshed successfully",,,
|
||||
"Are you sure wants to anonymize data ?.",,,
|
||||
"Are you sure wants to import demo data for '${name}'?",,,
|
||||
"Are you sure wants to install '${name}'?",,,
|
||||
"Are you sure wants to uninstall '${name}'?",,,
|
||||
"Backup",,,
|
||||
"Backup Created",,,
|
||||
"Backup File",,,
|
||||
"Backup On",,,
|
||||
"Backup Restore Error",,,
|
||||
"Backup Restored",,,
|
||||
"Bad header row:",,,
|
||||
"Bad import file",,,
|
||||
"Bulk install",,,
|
||||
"CSV",,,
|
||||
"CSV Backup Fetch Limit",,,
|
||||
"Cancelled",,,
|
||||
"Code",,,
|
||||
"Comment",,,
|
||||
"Condition",,,
|
||||
"ConditionParams",,,
|
||||
"Configure",,,
|
||||
"Create",,,
|
||||
"Create Backup",,,
|
||||
"Create file",,,
|
||||
"Data",,,
|
||||
"Data config line",,,
|
||||
"Delete",,,
|
||||
"Demo data loaded",,,
|
||||
"Demo data loaded successfully",,,
|
||||
"Depends on",,,
|
||||
"Description",,,
|
||||
"Details",,,
|
||||
"Draft",,,
|
||||
"Error in import. Please check log.",,,
|
||||
"Error in import: %s. Please check the server log",,,
|
||||
"Error in refreshing app",,,
|
||||
"Excel",,,
|
||||
"Export",,,
|
||||
"Export file",,,
|
||||
"Export format",,,
|
||||
"Exports",,,
|
||||
"Field",,,
|
||||
"Field permission",,,
|
||||
"Fields to delete",,,
|
||||
"Fields to export",,,
|
||||
"File",,,
|
||||
"Group Menu Assistant",,,
|
||||
"Groups",,,
|
||||
"Groups not found: %s",,,
|
||||
"Hide If",,,
|
||||
"Image",,,
|
||||
"Import",,,
|
||||
"Import ID",,,
|
||||
"Import access config",,,
|
||||
"Import completed successfully",,,
|
||||
"Import date",,,
|
||||
"Import demo data",,,
|
||||
"Import permissions",,,
|
||||
"Import roles",,,
|
||||
"Imported from",,,
|
||||
"In Progress",,,
|
||||
"Init data loaded",,,
|
||||
"Install",,,
|
||||
"Install order",,,
|
||||
"Installed",,,
|
||||
"Language",,,
|
||||
"Lines",,,
|
||||
"Log",,,
|
||||
"Menu not found: %s",,,
|
||||
"Menus",,,
|
||||
"Meta Permissions",,,
|
||||
"Model",,,
|
||||
"Model select",,,
|
||||
"Modules contains in the app",,,
|
||||
"Name",,,
|
||||
"No application language set. Please set 'application.locale' property.",,,
|
||||
"No configuration required",,,
|
||||
"No header row found",,,
|
||||
"No record found for: %s",,,
|
||||
"None",,,
|
||||
"Object",,,
|
||||
"Object data anonymize",,,
|
||||
"Object data config",,,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s",,,
|
||||
"Objects",,,
|
||||
"Path",,,
|
||||
"Path Field",,,
|
||||
"Permission Assistant",,,
|
||||
"Query",,,
|
||||
"Query/path",,,
|
||||
"Read",,,
|
||||
"Readonly If",,,
|
||||
"Record",,,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps",,,
|
||||
"Relative Date",,,
|
||||
"Replace",,,
|
||||
"Restore",,,
|
||||
"Restore Backup",,,
|
||||
"Restored On",,,
|
||||
"Return to draft status",,,
|
||||
"Roles",,,
|
||||
"Roles imported",,,
|
||||
"Roles imported successfully",,,
|
||||
"Roles not found: %s",,,
|
||||
"Select all",,,
|
||||
"Sequence",,,
|
||||
"Status",,,
|
||||
"Tab name",,,
|
||||
"Technical maintenance",,,
|
||||
"This app is used by %s. Please deactivate them before continue.",,,
|
||||
"Title",,,
|
||||
"Type",,,
|
||||
"Uninstall",,,
|
||||
"Update Import Id",,,
|
||||
"User",,,
|
||||
"User access config",,,
|
||||
"User field",,,
|
||||
"Validated",,,
|
||||
"Write",,,
|
||||
"{{$fmt('name')}}",,,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Zugriff auf die Konfiguration",,
|
||||
"Access config imported successfully","Zugriffskonfiguration erfolgreich importiert",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Daten anonymisieren",,
|
||||
"App","App",,
|
||||
"Application Config","Anwendungskonfiguration",,
|
||||
"Apps","Apps",,
|
||||
"Apps installed successfully","Apps erfolgreich installiert",,
|
||||
"Apps management","Apps-Verwaltung",,
|
||||
"Apps refreshed successfully","Apps erfolgreich aktualisiert",,
|
||||
"Are you sure wants to anonymize data ?.","Sind Sie sicher, dass Sie Daten anonymisieren möchten?.",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Sind Sie sicher, dass Sie Demo-Daten für '${name}' importieren möchten?",,
|
||||
"Are you sure wants to install '${name}'?","Sind Sie sicher, dass Sie '${name}' installieren möchten?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Sind Sie sicher, dass Sie '${name}' deinstallieren möchten?",,
|
||||
"Backup","Sicherung",,
|
||||
"Backup Created","Sicherung erstellt",,
|
||||
"Backup File","Sicherungsdatei",,
|
||||
"Backup On","Sicherung ein",,
|
||||
"Backup Restore Error","Fehler bei der Wiederherstellung des Backups",,
|
||||
"Backup Restored","Sicherung wiederhergestellt",,
|
||||
"Bad header row:","Schlechte Kopfzeile:",,
|
||||
"Bad import file","Fehlerhafte Importdatei",,
|
||||
"Bulk install","Masseninstallation",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","CSV-Backup Fetch-Limit für das Abrufen von CSV-Backups",,
|
||||
"Cancelled",,,
|
||||
"Code","Code",,
|
||||
"Comment",,,
|
||||
"Condition","Bedingung",,
|
||||
"ConditionParams","ConditionParams",,
|
||||
"Configure","Konfigurieren",,
|
||||
"Create","Erstellen",,
|
||||
"Create Backup","Backup erstellen",,
|
||||
"Create file","Datei erstellen",,
|
||||
"Data","Daten",,
|
||||
"Data config line","Datenkonfigurationszeile",,
|
||||
"Delete","Löschen",,
|
||||
"Demo data loaded","Demo-Daten geladen",,
|
||||
"Demo data loaded successfully","Demo-Daten erfolgreich geladen",,
|
||||
"Depends on","Abhängig von",,
|
||||
"Description","Beschreibung",,
|
||||
"Details","Details",,
|
||||
"Draft","Entwurf",,
|
||||
"Error in import. Please check log.","Fehler beim Import. Bitte überprüfen Sie das Protokoll.",,
|
||||
"Error in import: %s. Please check the server log","Fehler beim Import: %s. Bitte überprüfen Sie das Serverprotokoll.",,
|
||||
"Error in refreshing app","Fehler bei der Aktualisierung der App",,
|
||||
"Excel","Excel",,
|
||||
"Export","Exportieren",,
|
||||
"Export file","Exportdatei",,
|
||||
"Export format","Exportformat",,
|
||||
"Exports",,,
|
||||
"Field","Feld",,
|
||||
"Field permission","Feldberechtigung",,
|
||||
"Fields to delete","Zu löschende Felder",,
|
||||
"Fields to export",,,
|
||||
"File","Datei",,
|
||||
"Group Menu Assistant","Gruppenmenü-Assistent",,
|
||||
"Groups","Gruppen",,
|
||||
"Groups not found: %s","Gruppen nicht gefunden: %s",,
|
||||
"Hide If","Ausblenden wenn",,
|
||||
"Image","Bild",,
|
||||
"Import","Importieren",,
|
||||
"Import ID","Import-ID",,
|
||||
"Import access config","Zugangskonfiguration importieren",,
|
||||
"Import completed successfully","Import erfolgreich abgeschlossen",,
|
||||
"Import date","Importdatum",,
|
||||
"Import demo data","Demo-Daten importieren",,
|
||||
"Import permissions","Importberechtigungen",,
|
||||
"Import roles","Rollen importieren",,
|
||||
"Imported from","Importiert aus",,
|
||||
"In Progress","In Bearbeitung",,
|
||||
"Init data loaded","Init-Daten geladen",,
|
||||
"Install","Installieren",,
|
||||
"Install order","Installationsreihenfolge",,
|
||||
"Installed","Installiert",,
|
||||
"Language","Sprache",,
|
||||
"Lines","Linien",,
|
||||
"Log","Protokoll",,
|
||||
"Menu not found: %s","Menü nicht gefunden: %s",,
|
||||
"Menus","Menüs",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Modell",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Module enthält in der App",,
|
||||
"Name","Name",,
|
||||
"No application language set. Please set 'application.locale' property.","Keine Anwendungssprache eingestellt. Bitte setzen Sie die Eigenschaft'application.locale'.",,
|
||||
"No configuration required","Keine Konfiguration erforderlich",,
|
||||
"No header row found","Keine Kopfzeile gefunden",,
|
||||
"No record found for: %s","Kein Datensatz gefunden für: %s",,
|
||||
"None","Keine",,
|
||||
"Object","Objekt",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Objektdaten-Konfiguration",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Objekt nicht gefunden: %s",,
|
||||
"Objects","Objekte",,
|
||||
"Path","Pfad",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Berechtigungsassistent",,
|
||||
"Query","Anfrage",,
|
||||
"Query/path","Abfrage/Pfad",,
|
||||
"Read","Lesen",,
|
||||
"Readonly If","Nur lesbar wenn",,
|
||||
"Record","Aufzeichnung",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Apps aktualisieren",,
|
||||
"Relative Date",,,
|
||||
"Replace","Ersetzen",,
|
||||
"Restore","Wiederherstellen",,
|
||||
"Restore Backup","Backup wiederherstellen",,
|
||||
"Restored On","Wiederhergestellt am",,
|
||||
"Return to draft status","Zurück zum Entwurfsstatus",,
|
||||
"Roles","Rollen",,
|
||||
"Roles imported","Importierte Rollen",,
|
||||
"Roles imported successfully","Rollen erfolgreich importiert",,
|
||||
"Roles not found: %s","Rollen nicht gefunden: %s",,
|
||||
"Select all","Alle auswählen",,
|
||||
"Sequence","Sequenz",,
|
||||
"Status","Status",,
|
||||
"Tab name","Registerkartenname",,
|
||||
"Technical maintenance","Technische Wartung",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Diese App wird von %s verwendet. Bitte deaktivieren Sie diese, bevor Sie fortfahren.",,
|
||||
"Title","Titel",,
|
||||
"Type","Typ",,
|
||||
"Uninstall","Deinstallation",,
|
||||
"Update Import Id",,,
|
||||
"User","Benutzer",,
|
||||
"User access config","Benutzerzugriffskonfiguration",,
|
||||
"User field","Benutzerfeld",,
|
||||
"Validated",,,
|
||||
"Write","Schreiben",,
|
||||
"{{$fmt('name')}}","{{$fmt('name')}}}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config",,,
|
||||
"Access config imported successfully",,,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data",,,
|
||||
"App",,,
|
||||
"Application Config",,,
|
||||
"Apps",,,
|
||||
"Apps installed successfully",,,
|
||||
"Apps management",,,
|
||||
"Apps refreshed successfully",,,
|
||||
"Are you sure wants to anonymize data ?.",,,
|
||||
"Are you sure wants to import demo data for '${name}'?",,,
|
||||
"Are you sure wants to install '${name}'?",,,
|
||||
"Are you sure wants to uninstall '${name}'?",,,
|
||||
"Backup",,,
|
||||
"Backup Created",,,
|
||||
"Backup File",,,
|
||||
"Backup On",,,
|
||||
"Backup Restore Error",,,
|
||||
"Backup Restored",,,
|
||||
"Bad header row:",,,
|
||||
"Bad import file",,,
|
||||
"Bulk install",,,
|
||||
"CSV",,,
|
||||
"CSV Backup Fetch Limit",,,
|
||||
"Cancelled",,,
|
||||
"Code",,,
|
||||
"Comment",,,
|
||||
"Condition",,,
|
||||
"ConditionParams",,,
|
||||
"Configure",,,
|
||||
"Create",,,
|
||||
"Create Backup",,,
|
||||
"Create file",,,
|
||||
"Data",,,
|
||||
"Data config line",,,
|
||||
"Delete",,,
|
||||
"Demo data loaded",,,
|
||||
"Demo data loaded successfully",,,
|
||||
"Depends on",,,
|
||||
"Description",,,
|
||||
"Details",,,
|
||||
"Draft",,,
|
||||
"Error in import. Please check log.",,,
|
||||
"Error in import: %s. Please check the server log",,,
|
||||
"Error in refreshing app",,,
|
||||
"Excel",,,
|
||||
"Export",,,
|
||||
"Export file",,,
|
||||
"Export format",,,
|
||||
"Exports",,,
|
||||
"Field",,,
|
||||
"Field permission",,,
|
||||
"Fields to delete",,,
|
||||
"Fields to export",,,
|
||||
"File",,,
|
||||
"Group Menu Assistant",,,
|
||||
"Groups",,,
|
||||
"Groups not found: %s",,,
|
||||
"Hide If",,,
|
||||
"Image",,,
|
||||
"Import",,,
|
||||
"Import ID",,,
|
||||
"Import access config",,,
|
||||
"Import completed successfully",,,
|
||||
"Import date",,,
|
||||
"Import demo data",,,
|
||||
"Import permissions",,,
|
||||
"Import roles",,,
|
||||
"Imported from",,,
|
||||
"In Progress",,,
|
||||
"Init data loaded",,,
|
||||
"Install",,,
|
||||
"Install order",,,
|
||||
"Installed",,,
|
||||
"Language",,,
|
||||
"Lines",,,
|
||||
"Log",,,
|
||||
"Menu not found: %s",,,
|
||||
"Menus",,,
|
||||
"Meta Permissions",,,
|
||||
"Model",,,
|
||||
"Model select",,,
|
||||
"Modules contains in the app",,,
|
||||
"Name",,,
|
||||
"No application language set. Please set 'application.locale' property.",,,
|
||||
"No configuration required",,,
|
||||
"No header row found",,,
|
||||
"No record found for: %s",,,
|
||||
"None",,,
|
||||
"Object",,,
|
||||
"Object data anonymize",,,
|
||||
"Object data config",,,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s",,,
|
||||
"Objects",,,
|
||||
"Path",,,
|
||||
"Path Field",,,
|
||||
"Permission Assistant",,,
|
||||
"Query",,,
|
||||
"Query/path",,,
|
||||
"Read",,,
|
||||
"Readonly If",,,
|
||||
"Record",,,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps",,,
|
||||
"Relative Date",,,
|
||||
"Replace",,,
|
||||
"Restore",,,
|
||||
"Restore Backup",,,
|
||||
"Restored On",,,
|
||||
"Return to draft status",,,
|
||||
"Roles",,,
|
||||
"Roles imported",,,
|
||||
"Roles imported successfully",,,
|
||||
"Roles not found: %s",,,
|
||||
"Select all",,,
|
||||
"Sequence",,,
|
||||
"Status",,,
|
||||
"Tab name",,,
|
||||
"Technical maintenance",,,
|
||||
"This app is used by %s. Please deactivate them before continue.",,,
|
||||
"Title",,,
|
||||
"Type",,,
|
||||
"Uninstall",,,
|
||||
"Update Import Id",,,
|
||||
"User",,,
|
||||
"User access config",,,
|
||||
"User field",,,
|
||||
"Validated",,,
|
||||
"Write",,,
|
||||
"{{$fmt('name')}}",,,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Acceso config",,
|
||||
"Access config imported successfully","Configuración de acceso importada con éxito",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Anonimizar datos",,
|
||||
"App","Aplicación",,
|
||||
"Application Config","Configuración de la aplicación",,
|
||||
"Apps","Aplicaciones",,
|
||||
"Apps installed successfully","Aplicaciones instaladas con éxito",,
|
||||
"Apps management","Gestión de aplicaciones",,
|
||||
"Apps refreshed successfully","Aplicaciones actualizadas con éxito",,
|
||||
"Are you sure wants to anonymize data ?.","¿Está seguro de que quiere anonimizar los datos?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","¿Está seguro de que desea importar datos de demostración para '${name}'?",,
|
||||
"Are you sure wants to install '${name}'?","¿Estás seguro de que quieres instalar '${name}'?",,
|
||||
"Are you sure wants to uninstall '${name}'?","¿Estás seguro de que quieres desinstalar '${name}'?",,
|
||||
"Backup","Copia de seguridad",,
|
||||
"Backup Created","Copia de seguridad creada",,
|
||||
"Backup File","Archivo de copia de seguridad",,
|
||||
"Backup On","Copia de seguridad activada",,
|
||||
"Backup Restore Error","Error de restauración de copia de seguridad",,
|
||||
"Backup Restored","Copia de seguridad restaurada",,
|
||||
"Bad header row:","Fila de encabezado incorrecta:",,
|
||||
"Bad import file","Archivo de importación erróneo",,
|
||||
"Bulk install","Instalación a granel",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","Límite de obtención de copias de seguridad CSV",,
|
||||
"Cancelled",,,
|
||||
"Code","Código",,
|
||||
"Comment",,,
|
||||
"Condition","Condición",,
|
||||
"ConditionParams","ConditionParams",,
|
||||
"Configure","Configurar",,
|
||||
"Create","Crear",,
|
||||
"Create Backup","Crear copia de seguridad",,
|
||||
"Create file","Crear fichero",,
|
||||
"Data","Datos",,
|
||||
"Data config line","Línea de configuración de datos",,
|
||||
"Delete","Borrar",,
|
||||
"Demo data loaded","Datos de demostración cargados",,
|
||||
"Demo data loaded successfully","Datos de demostración cargados con éxito",,
|
||||
"Depends on","Depende de",,
|
||||
"Description","Descripción",,
|
||||
"Details","Detalles",,
|
||||
"Draft","Proyecto de",,
|
||||
"Error in import. Please check log.","Error en la importación. Por favor, compruebe el registro.",,
|
||||
"Error in import: %s. Please check the server log","Error en la importación: %s. Por favor, compruebe el registro del servidor",,
|
||||
"Error in refreshing app","Error en la actualización de la aplicación",,
|
||||
"Excel","Excel",,
|
||||
"Export","Exportación",,
|
||||
"Export file","Fichero de exportación",,
|
||||
"Export format","Formato de exportación",,
|
||||
"Exports",,,
|
||||
"Field","Campo",,
|
||||
"Field permission","Permiso de campo",,
|
||||
"Fields to delete","Campos a borrar",,
|
||||
"Fields to export",,,
|
||||
"File","Archivo",,
|
||||
"Group Menu Assistant","Asistente de menú de grupo",,
|
||||
"Groups","Grupos",,
|
||||
"Groups not found: %s","Grupos no encontrados: %s",,
|
||||
"Hide If","Ocultar si",,
|
||||
"Image","Imagen",,
|
||||
"Import","Importar",,
|
||||
"Import ID","ID de importación",,
|
||||
"Import access config","Configuración de acceso de importación",,
|
||||
"Import completed successfully","Importación completada con éxito",,
|
||||
"Import date","Fecha de importación",,
|
||||
"Import demo data","Importar datos de demostración",,
|
||||
"Import permissions","Permisos de importación",,
|
||||
"Import roles","Roles de importación",,
|
||||
"Imported from","Importado de",,
|
||||
"In Progress","En progreso",,
|
||||
"Init data loaded","Datos de inicio cargados",,
|
||||
"Install","Instalar",,
|
||||
"Install order","Orden de instalación",,
|
||||
"Installed","Instalado",,
|
||||
"Language","Idioma",,
|
||||
"Lines","Líneas",,
|
||||
"Log","Bitácora",,
|
||||
"Menu not found: %s","Menú no encontrado: %s",,
|
||||
"Menus","Menús",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Modelo",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Los módulos contienen en la aplicación",,
|
||||
"Name","Nombre",,
|
||||
"No application language set. Please set 'application.locale' property.","No se ha configurado ningún idioma de aplicación. Por favor, establezca la propiedad'application.locale'.",,
|
||||
"No configuration required","No requiere configuración",,
|
||||
"No header row found","No se ha encontrado ninguna línea de encabezado",,
|
||||
"No record found for: %s","No se han encontrado registros de: %s",,
|
||||
"None","Ninguno",,
|
||||
"Object","Objeto",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Datos del objeto config",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Objeto no encontrado: %s",,
|
||||
"Objects","Objetos",,
|
||||
"Path","Camino",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Asistente de Permisos",,
|
||||
"Query","Consulta",,
|
||||
"Query/path","Consulta/ruta",,
|
||||
"Read","Leer",,
|
||||
"Readonly If","Sólo lectura si",,
|
||||
"Record","Grabar",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Actualizar aplicaciones",,
|
||||
"Relative Date",,,
|
||||
"Replace","Reemplazar",,
|
||||
"Restore","Restaurar",,
|
||||
"Restore Backup","Restaurar copia de seguridad",,
|
||||
"Restored On","Restaurado el",,
|
||||
"Return to draft status","Volver al estado de borrador",,
|
||||
"Roles","Roles",,
|
||||
"Roles imported","Roles importados",,
|
||||
"Roles imported successfully","Roles importados con éxito",,
|
||||
"Roles not found: %s","Roles no encontrados: %s",,
|
||||
"Select all","Seleccionar todo",,
|
||||
"Sequence","Secuencia",,
|
||||
"Status","Estado",,
|
||||
"Tab name","Nombre de ficha",,
|
||||
"Technical maintenance","Mantenimiento técnico",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Esta aplicación es utilizada por %s. Por favor, desactívelas antes de continuar.",,
|
||||
"Title","Título",,
|
||||
"Type","Tipo",,
|
||||
"Uninstall","Desinstalar",,
|
||||
"Update Import Id",,,
|
||||
"User","Usuario",,
|
||||
"User access config","Acceso de usuario config",,
|
||||
"User field","Campo de usuario",,
|
||||
"Validated",,,
|
||||
"Write","Escribir",,
|
||||
"{{$fmt('name')}}","{{$fmt('nombre')}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config",,,
|
||||
"Access config imported successfully",,,
|
||||
"Action to perform","Action à effectuer",,
|
||||
"Anonymize","Anonymiser",,
|
||||
"Anonymize data","Anonymiser les données",,
|
||||
"App",,,
|
||||
"Application Config","Config applicative",,
|
||||
"Apps",,,
|
||||
"Apps installed successfully","Applications installées avec succès",,
|
||||
"Apps management","Gestion des applications",,
|
||||
"Apps refreshed successfully","Applications actualisées avec succès",,
|
||||
"Are you sure wants to anonymize data ?.","Voulez-vous vraiment rendre anonyme les données ?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Voulez-vous vraiment importer les données de démo '${name}'?",,
|
||||
"Are you sure wants to install '${name}'?","Voulez-vous vraiment intaller '${name}'?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Voulez-vous vraiment désintaller '${name}'?",,
|
||||
"Backup","Sauvegarde",,
|
||||
"Backup Created","Sauvegarde créée",,
|
||||
"Backup File","Fichier de sauvegarde",,
|
||||
"Backup On","Sauvegardé le",,
|
||||
"Backup Restore Error","Erreur lors de la restauration de sauvegarde",,
|
||||
"Backup Restored","Sauvegarde restaurée",,
|
||||
"Bad header row:","Ligne d'en-tête incorrecte",,
|
||||
"Bad import file","Fichier d'import incorrect",,
|
||||
"Bulk install","Installation en masse",,
|
||||
"CSV",,,
|
||||
"CSV Backup Fetch Limit",,,
|
||||
"Cancelled",,,
|
||||
"Code",,,
|
||||
"Comment",,,
|
||||
"Condition",,,
|
||||
"ConditionParams",,,
|
||||
"Configure","Configurer",,
|
||||
"Create","Créer",,
|
||||
"Create Backup","Créer une sauvegarde",,
|
||||
"Create file","Créer fichier",,
|
||||
"Data","Données",,
|
||||
"Data config line","Ligne de configuration de donnée",,
|
||||
"Delete","Supprimer",,
|
||||
"Demo data loaded","Charger les données de démonstration",,
|
||||
"Demo data loaded successfully","Données de démonstration chargées avec succès",,
|
||||
"Depends on","Dépend de",,
|
||||
"Description",,,
|
||||
"Details","Détails",,
|
||||
"Draft",,,
|
||||
"Error in import. Please check log.","Erreur dans l'import. Veuillez verifier le journal.",,
|
||||
"Error in import: %s. Please check the server log","Erreur dans l'import: %s. Veuillez verifier le journal du serveur",,
|
||||
"Error in refreshing app","Erreur dans l'actualisation de l'application",,
|
||||
"Excel",,,
|
||||
"Export","Exporter",,
|
||||
"Export file",,,
|
||||
"Export format","Format d'export",,
|
||||
"Exports",,,
|
||||
"Field","Champ",,
|
||||
"Field permission","Champ de permission",,
|
||||
"Fields to delete","Champs à supprimer",,
|
||||
"Fields to export","Champs à exporter",,
|
||||
"File","Fichier",,
|
||||
"Group Menu Assistant","Assistant gestion des menus par groupes",,
|
||||
"Groups","Groupes",,
|
||||
"Groups not found: %s","Groupe(s) inconnu(s) : %s",,
|
||||
"Hide If","Cacher si",,
|
||||
"Image",,,
|
||||
"Import","Importer",,
|
||||
"Import ID","ID d’import",,
|
||||
"Import access config",,,
|
||||
"Import completed successfully",,,
|
||||
"Import date","Date d'import",,
|
||||
"Import demo data","Importer les données de démonstration",,
|
||||
"Import permissions","Importer les permissions",,
|
||||
"Import roles",,,
|
||||
"Imported from","Importé depuis",,
|
||||
"In Progress",,,
|
||||
"Init data loaded","Charger les données d'initialisation",,
|
||||
"Install","Installer",,
|
||||
"Install order",,,
|
||||
"Installed",,,
|
||||
"Language","Langue",,
|
||||
"Lines",,,
|
||||
"Log","Journal",,
|
||||
"Menu not found: %s","Menu(s) inconnu(s) : %s",,
|
||||
"Menus",,,
|
||||
"Meta Permissions",,,
|
||||
"Model",,,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Modules contenus dans l'application",,
|
||||
"Name","Nom",,
|
||||
"No application language set. Please set 'application.locale' property.","Langue d'application non configurée. Veuillez configurer la propriété 'application.locale'",,
|
||||
"No configuration required","Pas de configuration requise",,
|
||||
"No header row found","Aucune ligne d'en-tête reconnue",,
|
||||
"No record found for: %s",,,
|
||||
"None",,,
|
||||
"Object","Objet",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Configuration des objets de données",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Objet(s) inconnu(s) : %s",,
|
||||
"Objects","Objets",,
|
||||
"Path","Chemin",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Assistant gestion des permissions",,
|
||||
"Query","Requête",,
|
||||
"Query/path","Requête/chemin",,
|
||||
"Read","Lecture",,
|
||||
"Readonly If","Lecture seule si",,
|
||||
"Record","Enregistrement",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Actualiser les applications",,
|
||||
"Relative Date",,,
|
||||
"Replace",,,
|
||||
"Restore","Restaurer",,
|
||||
"Restore Backup","Restaurer une sauvegarde",,
|
||||
"Restored On","Restauré le",,
|
||||
"Return to draft status","Retourner à l'état brouillon",,
|
||||
"Roles","Rôles",,
|
||||
"Roles imported","Rôles importés",,
|
||||
"Roles imported successfully","Rôle importés avec succès",,
|
||||
"Roles not found: %s","Rôles non trouvés: %s",,
|
||||
"Select all","Tout sélectionner",,
|
||||
"Sequence","Séquence",,
|
||||
"Status",,,
|
||||
"Tab name","Nom de l'onglet",,
|
||||
"Technical maintenance","Maintenance technique",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Cette application est utilisée par %s. Veuillez les désactiver avent de continuer",,
|
||||
"Title","Titre",,
|
||||
"Type",,,
|
||||
"Uninstall",,,
|
||||
"Update Import Id",,,
|
||||
"User",,,
|
||||
"User access config",,,
|
||||
"User field","Champ utilisateur",,
|
||||
"Validated",,,
|
||||
"Write","Écriture",,
|
||||
"{{$fmt('name')}}",,,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Configurazione degli accessi",,
|
||||
"Access config imported successfully","Accesso alla configurazione importata con successo",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Anonimizzare i dati",,
|
||||
"App","App",,
|
||||
"Application Config","Config dell'applicazione",,
|
||||
"Apps","Applicazioni",,
|
||||
"Apps installed successfully","Le applicazioni installate con successo",,
|
||||
"Apps management","Gestione delle applicazioni",,
|
||||
"Apps refreshed successfully","Le applicazioni aggiornate con successo",,
|
||||
"Are you sure wants to anonymize data ?.","Sei sicuro di voler rendere anonimi i dati?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Sei sicuro di voler importare i dati demo per '${name}'?",,
|
||||
"Are you sure wants to install '${name}'?","Sei sicuro di voler installare '${name}'?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Sei sicuro di voler disinstallare '${name}'?",,
|
||||
"Backup","Backup",,
|
||||
"Backup Created","Backup creato",,
|
||||
"Backup File","File di backup",,
|
||||
"Backup On","Backup On",,
|
||||
"Backup Restore Error","Errore di ripristino del backup",,
|
||||
"Backup Restored","Backup ripristinato",,
|
||||
"Bad header row:","Brutta fila di intestazioni:",,
|
||||
"Bad import file","File di importazione difettoso",,
|
||||
"Bulk install","Installazione all'ingrosso",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","Limite di recupero del backup CSV",,
|
||||
"Cancelled",,,
|
||||
"Code","Codice",,
|
||||
"Comment",,,
|
||||
"Condition","Condizione",,
|
||||
"ConditionParams","CondizioneParigi",,
|
||||
"Configure","Configurare",,
|
||||
"Create","Creare",,
|
||||
"Create Backup","Crea backup",,
|
||||
"Create file","Crea file",,
|
||||
"Data","Dati",,
|
||||
"Data config line","Linea di configurazione dati",,
|
||||
"Delete","Cancellare",,
|
||||
"Demo data loaded","Dati demo caricati",,
|
||||
"Demo data loaded successfully","Dati dimostrativi caricati con successo",,
|
||||
"Depends on","Dipende",,
|
||||
"Description","Descrizione",,
|
||||
"Details","Dettagli",,
|
||||
"Draft","Bozza",,
|
||||
"Error in import. Please check log.","Errore nell'importazione. Per favore, controlla il registro.",,
|
||||
"Error in import: %s. Please check the server log","Errore nell'importazione: %s. Controllare il log del server",,
|
||||
"Error in refreshing app","Errore nell'applicazione rinfrescante",,
|
||||
"Excel","Excel",,
|
||||
"Export","Esportazione",,
|
||||
"Export file","Esporta file",,
|
||||
"Export format","Formato di esportazione",,
|
||||
"Exports",,,
|
||||
"Field","Campo",,
|
||||
"Field permission","Permesso di campo",,
|
||||
"Fields to delete","Campi da cancellare",,
|
||||
"Fields to export",,,
|
||||
"File","File",,
|
||||
"Group Menu Assistant","Assistente menu di gruppo",,
|
||||
"Groups","Gruppi",,
|
||||
"Groups not found: %s","Gruppi non trovati: %s",,
|
||||
"Hide If","Nascondi se",,
|
||||
"Image","Immagine",,
|
||||
"Import","Importazione",,
|
||||
"Import ID","Importa ID",,
|
||||
"Import access config","Importare la configurazione di accesso",,
|
||||
"Import completed successfully","Importazione completata con successo",,
|
||||
"Import date","Data d'importazione",,
|
||||
"Import demo data","Importare i dati demo",,
|
||||
"Import permissions","Autorizzazioni all'importazione",,
|
||||
"Import roles","Ruoli di importazione",,
|
||||
"Imported from","Importati da",,
|
||||
"In Progress","In corso",,
|
||||
"Init data loaded","Dati init caricati",,
|
||||
"Install","Installare",,
|
||||
"Install order","Ordine di installazione",,
|
||||
"Installed","Installato",,
|
||||
"Language","Lingua",,
|
||||
"Lines","Linee",,
|
||||
"Log","Tronco",,
|
||||
"Menu not found: %s","Menu non trovato: %s",,
|
||||
"Menus","Menu",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Modello",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","I moduli contengono nell'applicazione",,
|
||||
"Name","Nome",,
|
||||
"No application language set. Please set 'application.locale' property.","Nessuna lingua di applicazione impostata. Impostare la proprietà 'application.locale'.",,
|
||||
"No configuration required","Nessuna configurazione richiesta",,
|
||||
"No header row found","Nessuna riga di intestazione trovata",,
|
||||
"No record found for: %s","Nessun record trovato: %s",,
|
||||
"None","Nessuna",,
|
||||
"Object","Oggetto",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Configurazione dei dati dell'oggetto",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Oggetto non trovato: %s",,
|
||||
"Objects","Oggetti",,
|
||||
"Path","Sentiero",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Assistente di autorizzazione",,
|
||||
"Query","Domanda",,
|
||||
"Query/path","Domanda/percorso",,
|
||||
"Read","Leggi",,
|
||||
"Readonly If","Leggero Se",,
|
||||
"Record","Registrare",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Aggiorna le applicazioni",,
|
||||
"Relative Date",,,
|
||||
"Replace","Sostituire",,
|
||||
"Restore","Ripristinare",,
|
||||
"Restore Backup","Ripristina backup",,
|
||||
"Restored On","Ripristinato su",,
|
||||
"Return to draft status","Ritorno allo stato di bozza",,
|
||||
"Roles","Ruoli",,
|
||||
"Roles imported","Ruoli importati",,
|
||||
"Roles imported successfully","Ruoli importati con successo",,
|
||||
"Roles not found: %s","Ruoli non trovati: %s",,
|
||||
"Select all","Seleziona tutti",,
|
||||
"Sequence","Sequenza",,
|
||||
"Status","Stato",,
|
||||
"Tab name","Nome della scheda",,
|
||||
"Technical maintenance","Manutenzione tecnica",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Questa applicazione è usata da %s. Disattivarli prima di continuare.",,
|
||||
"Title","Titolo",,
|
||||
"Type","Tipo",,
|
||||
"Uninstall","Disinstallare",,
|
||||
"Update Import Id",,,
|
||||
"User","Utente",,
|
||||
"User access config","Configurazione dell'accesso utente",,
|
||||
"User field","Campo utente",,
|
||||
"Validated",,,
|
||||
"Write","Scrivere",,
|
||||
"{{$fmt('name')}}","{{$fmt('name')}}}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Toegang configureren",,
|
||||
"Access config imported successfully","Toegang tot geïmporteerde configuratie met succes",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Gegevens anonimiseren",,
|
||||
"App","App",,
|
||||
"Application Config","Toepassingsconfiguratie",,
|
||||
"Apps","Apps",,
|
||||
"Apps installed successfully","Apps succesvol geïnstalleerd",,
|
||||
"Apps management","Beheer van apps",,
|
||||
"Apps refreshed successfully","Apps succesvol ververst",,
|
||||
"Are you sure wants to anonymize data ?.","Weet u zeker dat u gegevens wilt anonimiseren?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Weet u zeker dat u demodata wilt importeren voor '${naam}'?",,
|
||||
"Are you sure wants to install '${name}'?","Weet u zeker dat u '${naam}' wilt installeren?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Weet u zeker dat u '${naam}' wilt verwijderen?",,
|
||||
"Backup","Back-up",,
|
||||
"Backup Created","Back-up gemaakt",,
|
||||
"Backup File","Back-up bestand",,
|
||||
"Backup On","Backup Aan",,
|
||||
"Backup Restore Error","Back-up Fout herstellen",,
|
||||
"Backup Restored","Back-up hersteld",,
|
||||
"Bad header row:","Slechte header-rij:",,
|
||||
"Bad import file","Slecht importbestand",,
|
||||
"Bulk install","Bulk installeren",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","CSV-back-up limiet halen",,
|
||||
"Cancelled",,,
|
||||
"Code","Code",,
|
||||
"Comment",,,
|
||||
"Condition","Voorwaarde",,
|
||||
"ConditionParams","ConditionParams",,
|
||||
"Configure","Configureer",,
|
||||
"Create","Creëer",,
|
||||
"Create Backup","Back-up maken",,
|
||||
"Create file","Bestand aanmaken",,
|
||||
"Data","Gegevens",,
|
||||
"Data config line","Gegevensconfiguratielijn",,
|
||||
"Delete","Schrappen",,
|
||||
"Demo data loaded","Demo-gegevens geladen",,
|
||||
"Demo data loaded successfully","Demodata succesvol geladen",,
|
||||
"Depends on","Afhankelijk van",,
|
||||
"Description","Beschrijving",,
|
||||
"Details","Details",,
|
||||
"Draft","Ontwerp",,
|
||||
"Error in import. Please check log.","Fout in de import. Controleer het logboek.",,
|
||||
"Error in import: %s. Please check the server log","Fout in de invoer: %s. Controleer het serverlogboek",,
|
||||
"Error in refreshing app","Fout in verfrissende app",,
|
||||
"Excel","Excel",,
|
||||
"Export","Exporteren",,
|
||||
"Export file","Bestand exporteren",,
|
||||
"Export format","Exportformaat",,
|
||||
"Exports",,,
|
||||
"Field","Veld",,
|
||||
"Field permission","Veldtoestemming",,
|
||||
"Fields to delete","Te verwijderen velden",,
|
||||
"Fields to export",,,
|
||||
"File","Bestand",,
|
||||
"Group Menu Assistant","Assistent groepsmenu",,
|
||||
"Groups","Groepen",,
|
||||
"Groups not found: %s","Groepen niet gevonden: %s",,
|
||||
"Hide If","Verberg als",,
|
||||
"Image","Beeld",,
|
||||
"Import","Importeren",,
|
||||
"Import ID","ID importeren",,
|
||||
"Import access config","Importeer toegang configureren",,
|
||||
"Import completed successfully","Importeren succesvol afgerond",,
|
||||
"Import date","Datum van invoer",,
|
||||
"Import demo data","Demodata importeren",,
|
||||
"Import permissions","Importvergunningen",,
|
||||
"Import roles","Rollen importeren",,
|
||||
"Imported from","Geïmporteerd van",,
|
||||
"In Progress","In uitvoering",,
|
||||
"Init data loaded","Init gegevens geladen",,
|
||||
"Install","Installeren",,
|
||||
"Install order","Bestelling installeren",,
|
||||
"Installed","Geïnstalleerd",,
|
||||
"Language","Taal",,
|
||||
"Lines","Lijnen",,
|
||||
"Log","Logboek",,
|
||||
"Menu not found: %s","Menu niet gevonden: %s",,
|
||||
"Menus","Menu's",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Model",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Modules bevat in de app",,
|
||||
"Name","Naam",,
|
||||
"No application language set. Please set 'application.locale' property.","Geen applicatietaal ingesteld. Stel a.u.b. de eigenschap 'applicatie.locale' in.",,
|
||||
"No configuration required","Geen configuratie nodig",,
|
||||
"No header row found","Geen kopregel gevonden",,
|
||||
"No record found for: %s","Geen record gevonden voor: %s",,
|
||||
"None","Geen",,
|
||||
"Object","Voorwerp",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Objectgegevens configureren",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Object niet gevonden: %s",,
|
||||
"Objects","Voorwerpen",,
|
||||
"Path","Pad",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Toestemming Assistent",,
|
||||
"Query","Vraag",,
|
||||
"Query/path","Vraag/pad",,
|
||||
"Read","Lezen",,
|
||||
"Readonly If","Alleen lezen als",,
|
||||
"Record","Verslag",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Apps verversen",,
|
||||
"Relative Date",,,
|
||||
"Replace","Vervangen",,
|
||||
"Restore","Herstel",,
|
||||
"Restore Backup","Back-up herstellen",,
|
||||
"Restored On","Gerestaureerd op",,
|
||||
"Return to draft status","Terug naar de ontwerpstatus",,
|
||||
"Roles","Rollen",,
|
||||
"Roles imported","Geïmporteerde rollen",,
|
||||
"Roles imported successfully","Rollen succesvol geïmporteerd",,
|
||||
"Roles not found: %s","Rollen niet gevonden: %s",,
|
||||
"Select all","Selecteer alle",,
|
||||
"Sequence","Volgorde",,
|
||||
"Status","Status",,
|
||||
"Tab name","Tabblad naam",,
|
||||
"Technical maintenance","Technisch onderhoud",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Deze app wordt gebruikt door %s. Deactiveer ze voordat u verdergaat.",,
|
||||
"Title","Titel",,
|
||||
"Type","Type",,
|
||||
"Uninstall","Verwijderen",,
|
||||
"Update Import Id",,,
|
||||
"User","Gebruiker",,
|
||||
"User access config","Configuratie van de gebruikerstoegang",,
|
||||
"User field","Gebruikersveld",,
|
||||
"Validated",,,
|
||||
"Write","Schrijf",,
|
||||
"{{$fmt('name')}}","{{$fmt('naam')}}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Konfiguracja dostępu",,
|
||||
"Access config imported successfully","Konfiguracja dostępu zaimportowana pomyślnie",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Anonimizacja danych",,
|
||||
"App","Aplikacja",,
|
||||
"Application Config","Konfiguracja aplikacji",,
|
||||
"Apps","Aplikacje",,
|
||||
"Apps installed successfully","Aplikacje zainstalowane pomyślnie",,
|
||||
"Apps management","Zarządzanie aplikacjami",,
|
||||
"Apps refreshed successfully","Aplikacje odświeżone pomyślnie",,
|
||||
"Are you sure wants to anonymize data ?.","Czy na pewno chcesz anonimizować dane ?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Czy na pewno chcesz zaimportować dane demo dla '${nazwa}'?",,
|
||||
"Are you sure wants to install '${name}'?","Czy na pewno chcesz zainstalować '${nazwa}'?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Czy na pewno chcesz odinstalować '${nazwa}'?",,
|
||||
"Backup","Kopia zapasowa",,
|
||||
"Backup Created","Tworzenie kopii zapasowej",,
|
||||
"Backup File","Plik kopii zapasowej",,
|
||||
"Backup On","Kopia zapasowa Wł.",,
|
||||
"Backup Restore Error","Błąd kopii zapasowej",,
|
||||
"Backup Restored","Kopia zapasowa przywrócona",,
|
||||
"Bad header row:","Zły rząd nagłówka:",,
|
||||
"Bad import file","Zły plik importowy",,
|
||||
"Bulk install","Instalacja masowa",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","CSV Backup Fetch Limit",,
|
||||
"Cancelled",,,
|
||||
"Code","Kod",,
|
||||
"Comment",,,
|
||||
"Condition","Warunek",,
|
||||
"ConditionParams","ConditionParams",,
|
||||
"Configure","Konfiguracja",,
|
||||
"Create","Utworzyć",,
|
||||
"Create Backup","Tworzenie kopii zapasowej",,
|
||||
"Create file","Utwórz plik",,
|
||||
"Data","Dane",,
|
||||
"Data config line","Linia konfiguracji danych",,
|
||||
"Delete","Skreślenie",,
|
||||
"Demo data loaded","Załadowane dane demonstracyjne",,
|
||||
"Demo data loaded successfully","Udane wczytanie danych demonstracyjnych",,
|
||||
"Depends on","W zależności od",,
|
||||
"Description","Opis",,
|
||||
"Details","Szczegóły",,
|
||||
"Draft","Wstępny projekt",,
|
||||
"Error in import. Please check log.","Błąd w imporcie. Proszę sprawdzić dziennik.",,
|
||||
"Error in import: %s. Please check the server log","Błąd w imporcie: %s. Proszę sprawdzić log serwera",,
|
||||
"Error in refreshing app","Błąd w odświeżaniu aplikacji",,
|
||||
"Excel","Excel",,
|
||||
"Export","Eksportowanie",,
|
||||
"Export file","Eksport pliku",,
|
||||
"Export format","Format eksportu",,
|
||||
"Exports",,,
|
||||
"Field","Pole",,
|
||||
"Field permission","Pozwolenie w terenie",,
|
||||
"Fields to delete","Pola do usunięcia",,
|
||||
"Fields to export",,,
|
||||
"File","Pilnik",,
|
||||
"Group Menu Assistant","Asystent menu grupy",,
|
||||
"Groups","Grupy",,
|
||||
"Groups not found: %s","Grupy nie znaleziono: %s",,
|
||||
"Hide If","Ukryj Jeśli",,
|
||||
"Image","Obrazek",,
|
||||
"Import","Przywóz",,
|
||||
"Import ID","Identyfikator przywozu",,
|
||||
"Import access config","Konfiguracja dostępu do importu",,
|
||||
"Import completed successfully","Przywóz ukończony pomyślnie",,
|
||||
"Import date","Data przywozu",,
|
||||
"Import demo data","Import danych demo",,
|
||||
"Import permissions","Pozwolenia na przywóz",,
|
||||
"Import roles","Role importowe",,
|
||||
"Imported from","Przywożone z",,
|
||||
"In Progress","W toku",,
|
||||
"Init data loaded","Załadowane dane init",,
|
||||
"Install","Zainstalować",,
|
||||
"Install order","Zainstaluj zamówienie",,
|
||||
"Installed","Zainstalowane",,
|
||||
"Language","Język",,
|
||||
"Lines","Linie",,
|
||||
"Log","Dziennik",,
|
||||
"Menu not found: %s","Menu nie znaleziono: %s",,
|
||||
"Menus","Menu",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Model",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Moduły zawierają w aplikacji",,
|
||||
"Name","Nazwa",,
|
||||
"No application language set. Please set 'application.locale' property.","Brak zestawu języka aplikacji. Proszę ustawić 'application.locale' property.",,
|
||||
"No configuration required","Nie jest wymagana żadna konfiguracja.",,
|
||||
"No header row found","Nie znaleziono żadnego rzędu nagłówków",,
|
||||
"No record found for: %s","Nie znaleziono żadnego rekordu: %s",,
|
||||
"None","Brak.",,
|
||||
"Object","Obiekt",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Konfiguracja danych obiektowych",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Przedmiot nie znaleziono: %s",,
|
||||
"Objects","Przedmioty",,
|
||||
"Path","Ścieżka",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Asystent ds. pozwoleń",,
|
||||
"Query","Zapytanie",,
|
||||
"Query/path","Kwerenda/ścieżka",,
|
||||
"Read","Przeczytać",,
|
||||
"Readonly If","Czytelnie Jeśli",,
|
||||
"Record","Rekord",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Odśwież aplikacje",,
|
||||
"Relative Date",,,
|
||||
"Replace","Zastąpić miejsce",,
|
||||
"Restore","Przywróćcie stan poprzedniego",,
|
||||
"Restore Backup","Przywracanie kopii zapasowej",,
|
||||
"Restored On","Restored On (Przywrócono)",,
|
||||
"Return to draft status","Powrót do projektu statusu",,
|
||||
"Roles","Role",,
|
||||
"Roles imported","Role importowane",,
|
||||
"Roles imported successfully","Role importowane z powodzeniem",,
|
||||
"Roles not found: %s","Nie znaleziono ról: %s",,
|
||||
"Select all","Wybierz wszystkie",,
|
||||
"Sequence","Kolejność",,
|
||||
"Status","Status",,
|
||||
"Tab name","Nazwa zakładki",,
|
||||
"Technical maintenance","Konserwacja techniczna",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Ta aplikacja jest używana przez %s. Proszę je wyłączyć przed kontynuacją.",,
|
||||
"Title","Tytuł",,
|
||||
"Type","Typ",,
|
||||
"Uninstall","Odinstaluj",,
|
||||
"Update Import Id",,,
|
||||
"User","Użytkownik",,
|
||||
"User access config","Konfiguracja dostępu użytkownika",,
|
||||
"User field","Pole użytkownika",,
|
||||
"Validated",,,
|
||||
"Write","Napisać",,
|
||||
"{{$fmt('name')}}","{{{$fmt('name')}}}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Configuração de acesso",,
|
||||
"Access config imported successfully","Acesso à configuração importada com sucesso",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Anonimizar dados",,
|
||||
"App","Aplicativo",,
|
||||
"Application Config","Configuração da aplicação",,
|
||||
"Apps","Aplicativos",,
|
||||
"Apps installed successfully","Aplicativos instalados com sucesso",,
|
||||
"Apps management","Gestão de aplicações",,
|
||||
"Apps refreshed successfully","Aplicativos atualizados com sucesso",,
|
||||
"Are you sure wants to anonymize data ?.","Tens a certeza que queres anonimizar os dados?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Você tem certeza de que deseja importar dados de demonstração para '${nome}'?",,
|
||||
"Are you sure wants to install '${name}'?","Tens a certeza que queres instalar ${nome}?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Tens a certeza que queres desinstalar o '${nome}'?",,
|
||||
"Backup","Backup",,
|
||||
"Backup Created","Backup criado",,
|
||||
"Backup File","Arquivo de backup",,
|
||||
"Backup On","Backup On",,
|
||||
"Backup Restore Error","Erro de Restauração de Backup",,
|
||||
"Backup Restored","Backup restaurado",,
|
||||
"Bad header row:","Linha de cabeçalho ruim:",,
|
||||
"Bad import file","Arquivo de importação ruim",,
|
||||
"Bulk install","Instalação a granel",,
|
||||
"CSV","CSV",,
|
||||
"CSV Backup Fetch Limit","Limite de Busca de Apoio CSV",,
|
||||
"Cancelled",,,
|
||||
"Code","Código",,
|
||||
"Comment",,,
|
||||
"Condition","Condição",,
|
||||
"ConditionParams","CondiçãoParams",,
|
||||
"Configure","Configurar",,
|
||||
"Create","Criar",,
|
||||
"Create Backup","Criar backup",,
|
||||
"Create file","Criar arquivo",,
|
||||
"Data","Dados",,
|
||||
"Data config line","Linha de configuração de dados",,
|
||||
"Delete","Eliminar",,
|
||||
"Demo data loaded","Dados de demonstração carregados",,
|
||||
"Demo data loaded successfully","Dados de demonstração carregados com sucesso",,
|
||||
"Depends on","Depende de",,
|
||||
"Description","Descrição do produto",,
|
||||
"Details","Detalhes",,
|
||||
"Draft","Rascunho",,
|
||||
"Error in import. Please check log.","Erro na importação. Por favor, verifique o registo.",,
|
||||
"Error in import: %s. Please check the server log","Erro na importação: %s. Por favor, verifique o log do servidor",,
|
||||
"Error in refreshing app","Erro na atualização do aplicativo",,
|
||||
"Excel","Excel",,
|
||||
"Export","Exportação",,
|
||||
"Export file","Exportar arquivo",,
|
||||
"Export format","Formato de exportação",,
|
||||
"Exports",,,
|
||||
"Field","Campo",,
|
||||
"Field permission","Permissão de campo",,
|
||||
"Fields to delete","Campos a eliminar",,
|
||||
"Fields to export",,,
|
||||
"File","Arquivo",,
|
||||
"Group Menu Assistant","Assistente de Menu de Grupo",,
|
||||
"Groups","Grupos",,
|
||||
"Groups not found: %s","Grupos não encontrados: %s",,
|
||||
"Hide If","Ocultar se",,
|
||||
"Image","Imagem",,
|
||||
"Import","Importação",,
|
||||
"Import ID","Importar ID",,
|
||||
"Import access config","Importar configuração de acesso",,
|
||||
"Import completed successfully","Importação concluída com sucesso",,
|
||||
"Import date","Data de importação",,
|
||||
"Import demo data","Importar dados de demonstração",,
|
||||
"Import permissions","Permissões de importação",,
|
||||
"Import roles","Importar funções",,
|
||||
"Imported from","Importado de",,
|
||||
"In Progress","Em andamento",,
|
||||
"Init data loaded","Dados de inicialização carregados",,
|
||||
"Install","Instalar",,
|
||||
"Install order","Instalar ordem",,
|
||||
"Installed","Instalado",,
|
||||
"Language","Idioma",,
|
||||
"Lines","Linhas",,
|
||||
"Log","Log",,
|
||||
"Menu not found: %s","Menu não encontrado: %s",,
|
||||
"Menus","Menus",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Modelo",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Módulos contém no aplicativo",,
|
||||
"Name","Nome e Sobrenome",,
|
||||
"No application language set. Please set 'application.locale' property.","Nenhuma linguagem de aplicação definida. Por favor, defina a propriedade 'application.locale'.",,
|
||||
"No configuration required","Nenhuma configuração necessária",,
|
||||
"No header row found","Nenhuma linha de cabeçalho encontrada",,
|
||||
"No record found for: %s","Nenhum registo encontrado: %s",,
|
||||
"None","Nenhum",,
|
||||
"Object","Objeto",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Configuração dos dados do objeto",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Objeto não encontrado: %s",,
|
||||
"Objects","Objetos",,
|
||||
"Path","Caminho",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Assistente de permissão",,
|
||||
"Query","Consulta",,
|
||||
"Query/path","Consulta/caminho",,
|
||||
"Read","Ler",,
|
||||
"Readonly If","Apenas leitura Se",,
|
||||
"Record","Registrar",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Atualizar aplicativos",,
|
||||
"Relative Date",,,
|
||||
"Replace","Substituir",,
|
||||
"Restore","Restaurar",,
|
||||
"Restore Backup","Restaurar backup",,
|
||||
"Restored On","Restaurado em",,
|
||||
"Return to draft status","Retornar ao status de esboço",,
|
||||
"Roles","Funções",,
|
||||
"Roles imported","Funções importadas",,
|
||||
"Roles imported successfully","Funções importadas com sucesso",,
|
||||
"Roles not found: %s","Funções não encontradas: %s",,
|
||||
"Select all","Selecionar todos",,
|
||||
"Sequence","Seqüência",,
|
||||
"Status","Estado",,
|
||||
"Tab name","Nome da guia",,
|
||||
"Technical maintenance","Manutenção técnica",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Este aplicativo é usado por %s. Por favor, desactive-os antes de continuar.",,
|
||||
"Title","Título",,
|
||||
"Type","Tipo de",,
|
||||
"Uninstall","Desinstalar",,
|
||||
"Update Import Id",,,
|
||||
"User","Usuário",,
|
||||
"User access config","Configuração de acesso do usuário",,
|
||||
"User field","Campo do usuário",,
|
||||
"Validated",,,
|
||||
"Write","Escrever",,
|
||||
"{{$fmt('name')}}","{{$fmt('name')}}",,
|
||||
|
@ -0,0 +1,136 @@
|
||||
"key","message","comment","context"
|
||||
"Access config","Конфигурация доступа",,
|
||||
"Access config imported successfully","Доступ к успешно импортированной конфигурации доступа",,
|
||||
"Action to perform",,,
|
||||
"Anonymize",,,
|
||||
"Anonymize data","Анонимные данные",,
|
||||
"App","Приложение",,
|
||||
"Application Config","Настройка приложений",,
|
||||
"Apps","Приложения",,
|
||||
"Apps installed successfully","Успешно установленные приложения",,
|
||||
"Apps management","Управление приложениями",,
|
||||
"Apps refreshed successfully","Приложения успешно обновились",,
|
||||
"Are you sure wants to anonymize data ?.","Вы уверены, что хотите обезличить данные?",,
|
||||
"Are you sure wants to import demo data for '${name}'?","Вы уверены, что хотите импортировать демо-данные для '${имя}'?",,
|
||||
"Are you sure wants to install '${name}'?","Ты уверен, что хочешь установить '${имя}'?",,
|
||||
"Are you sure wants to uninstall '${name}'?","Ты уверен, что хочешь удалить '${имя}'?",,
|
||||
"Backup","Резервное копирование",,
|
||||
"Backup Created","Резервное копирование Создано",,
|
||||
"Backup File","Резервный файл",,
|
||||
"Backup On","Резервное копирование Вкл",,
|
||||
"Backup Restore Error","Резервное копирование Восстановление Ошибка",,
|
||||
"Backup Restored","Резервное копирование Восстановлено",,
|
||||
"Bad header row:","Плохой заголовок:",,
|
||||
"Bad import file","Плохой файл импорта",,
|
||||
"Bulk install","Массовая установка",,
|
||||
"CSV","РЕЗЮМЕ",,
|
||||
"CSV Backup Fetch Limit","Лимит на резервное копирование CSV",,
|
||||
"Cancelled",,,
|
||||
"Code","Код",,
|
||||
"Comment",,,
|
||||
"Condition","Условие",,
|
||||
"ConditionParams","УсловияПарамы",,
|
||||
"Configure","Настроить",,
|
||||
"Create","Создать",,
|
||||
"Create Backup","Создать резервное копирование",,
|
||||
"Create file","Создать файл",,
|
||||
"Data","Данные",,
|
||||
"Data config line","Строка конфигурирования данных",,
|
||||
"Delete","Удалить",,
|
||||
"Demo data loaded","Загружены демонстрационные данные",,
|
||||
"Demo data loaded successfully","Успешная загрузка демонстрационных данных",,
|
||||
"Depends on","Зависит от",,
|
||||
"Description","Описание",,
|
||||
"Details","Детали",,
|
||||
"Draft","Проект",,
|
||||
"Error in import. Please check log.","Ошибка при импорте. Пожалуйста, проверьте журнал.",,
|
||||
"Error in import: %s. Please check the server log","Ошибка при импорте: %s. Пожалуйста, проверьте журнал сервера",,
|
||||
"Error in refreshing app","Ошибка в обновляющемся приложении",,
|
||||
"Excel","Excel",,
|
||||
"Export","Экспорт",,
|
||||
"Export file","Экспорт файла",,
|
||||
"Export format","Формат экспорта",,
|
||||
"Exports",,,
|
||||
"Field","Поле",,
|
||||
"Field permission","Разрешение на эксплуатацию",,
|
||||
"Fields to delete","Поля для удаления",,
|
||||
"Fields to export",,,
|
||||
"File","Файл",,
|
||||
"Group Menu Assistant","Помощник по меню в группе",,
|
||||
"Groups","Группы",,
|
||||
"Groups not found: %s","Не найденные группы: %s",,
|
||||
"Hide If","Скрыть Если",,
|
||||
"Image","Изображение",,
|
||||
"Import","Импорт",,
|
||||
"Import ID","Импорт ID",,
|
||||
"Import access config","Импорт конфигурации доступа к импорту",,
|
||||
"Import completed successfully","Успешно завершен импорт",,
|
||||
"Import date","Дата импорта",,
|
||||
"Import demo data","Импорт демонстрационных данных",,
|
||||
"Import permissions","Разрешения на импорт",,
|
||||
"Import roles","Важные роли",,
|
||||
"Imported from","Импортировано из",,
|
||||
"In Progress","В процессе",,
|
||||
"Init data loaded","Загружены исходные данные",,
|
||||
"Install","Установить",,
|
||||
"Install order","Установка заказа",,
|
||||
"Installed","Установленный",,
|
||||
"Language","Язык",,
|
||||
"Lines","Линии",,
|
||||
"Log","Журнал",,
|
||||
"Menu not found: %s","Меню не найдено: %s",,
|
||||
"Menus","Меню",,
|
||||
"Meta Permissions",,,
|
||||
"Model","Модель",,
|
||||
"Model select",,,
|
||||
"Modules contains in the app","Модули, содержащиеся в приложении",,
|
||||
"Name","Имя",,
|
||||
"No application language set. Please set 'application.locale' property.","Язык приложения не установлен. Пожалуйста, установите свойство 'application.locale'.",,
|
||||
"No configuration required","Конфигурация не требуется",,
|
||||
"No header row found","Заголовок строки не найден",,
|
||||
"No record found for: %s","Записи не найдено: %s",,
|
||||
"None","Нет",,
|
||||
"Object","Объект",,
|
||||
"Object data anonymize",,,
|
||||
"Object data config","Конфигурация данных объекта",,
|
||||
"Object data config export",,,
|
||||
"Object data config exports",,,
|
||||
"Object not found: %s","Объект не найден: %s",,
|
||||
"Objects","Объекты",,
|
||||
"Path","Путь",,
|
||||
"Path Field",,,
|
||||
"Permission Assistant","Помощник по разрешениям",,
|
||||
"Query","Запрос",,
|
||||
"Query/path","Запрос/путь",,
|
||||
"Read","Читать",,
|
||||
"Readonly If","Только для чтения Если",,
|
||||
"Record","Запись",,
|
||||
"Record name",,,
|
||||
"Record select",,,
|
||||
"Refresh Apps","Обновить приложения",,
|
||||
"Relative Date",,,
|
||||
"Replace","Заменить",,
|
||||
"Restore","Восстановить",,
|
||||
"Restore Backup","Восстановить резервное копирование",,
|
||||
"Restored On","Восстановлен вкл.",,
|
||||
"Return to draft status","Возврат к статусу черновика",,
|
||||
"Roles","Роли",,
|
||||
"Roles imported","Импортированные роли",,
|
||||
"Roles imported successfully","Роли успешно импортированы",,
|
||||
"Roles not found: %s","Роли не найдены: %s",,
|
||||
"Select all","Выберите все",,
|
||||
"Sequence","Последовательность",,
|
||||
"Status","Статус",,
|
||||
"Tab name","Имя вкладки",,
|
||||
"Technical maintenance","Техническое обслуживание",,
|
||||
"This app is used by %s. Please deactivate them before continue.","Это приложение используется в %s. Пожалуйста, деактивируйте их перед продолжением.",,
|
||||
"Title","Название",,
|
||||
"Type","Тип",,
|
||||
"Uninstall","Удалить",,
|
||||
"Update Import Id",,,
|
||||
"User","Пользователь",,
|
||||
"User access config","Конфигурация доступа пользователя",,
|
||||
"User field","Поле пользователя",,
|
||||
"Validated",,,
|
||||
"Write","Напиши",,
|
||||
"{{$fmt('name')}}","{{$fmt('name')}}",,
|
||||
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<csv-inputs xmlns="http://axelor.com/xml/ns/data-import"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
|
||||
|
||||
<input file="base_permission.csv" separator=";" type="com.axelor.auth.db.Permission" search="self.name = :name" call="com.axelor.csv.script.ImportPermission:importPermissionToRole">
|
||||
<bind to="canRead" eval="can_read == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canWrite" eval="can_write == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canCreate" eval="can_create == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canRemove" eval="can_remove == 'x' ? 'true' : 'false'"/>
|
||||
<bind to="canExport" eval="can_export == 'x' ? 'true' : 'false'"/>
|
||||
</input>
|
||||
|
||||
</csv-inputs>
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
"name";"object";"can_read";"can_write";"can_create";"can_remove";"can_export";"condition";"conditionParams";"roleName"
|
||||
"perm.auth.User.r";"com.axelor.auth.db.User";"x";;;;;;;"Base Read"
|
||||
"perm.auth.User.rwc";"com.axelor.auth.db.User";"x";"x";"x";;;"self.id = ?";"__user__.id";"Base User"
|
||||
"perm.auth.User.rwcde";"com.axelor.auth.db.User";"x";"x";"x";"x";"x";;;"Base Manager"
|
||||
"perm.meta.r";"com.axelor.meta.db.*";"x";;;;;;;"Base Manager"
|
||||
"perm.meta.r";"com.axelor.meta.db.*";"x";;;;;;;"Base User"
|
||||
"perm.meta.r";"com.axelor.meta.db.*";"x";;;;;;;"Base Read"
|
||||
"perm.mail.all";"com.axelor.mail.db.*";"x";"x";"x";"x";"x";;;"Base Manager"
|
||||
"perm.mail.rwc";"com.axelor.mail.db.*";"x";"x";"x";;;;;"Base User"
|
||||
"perm.mail.r";"com.axelor.mail.db.*";"x";;;;;;;"Base Read"
|
||||
"perm.dms.all";"com.axelor.dms.db.*";"x";"x";"x";"x";"x";;;"Base Manager"
|
||||
"perm.dms.rwc";"com.axelor.dms.db.*";"x";"x";"x";;;;;"Base User"
|
||||
"perm.dms.r";"com.axelor.dms.db.*";"x";;;;;;;"Base Read"
|
||||
"perm.team.all";"com.axelor.team.db.*";"x";"x";"x";"x";"x";;;"Base Manager"
|
||||
"perm.team.rwc";"com.axelor.team.db.*";"x";"x";"x";;;;;"Base User"
|
||||
"perm.team.r";"com.axelor.team.db.*";"x";;;;;;;"Base Read"
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="access-config-grid" title="Access config" model="com.axelor.apps.base.db.AccessConfig">
|
||||
<field name="name" />
|
||||
</grid>
|
||||
|
||||
<form name="access-config-form" title="Access config" model="com.axelor.apps.base.db.AccessConfig">
|
||||
<panel name="mainPanel">
|
||||
<field name="name" />
|
||||
<field name="roleSet" colSpan="12"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="app-grid" title="Apps" model="com.axelor.apps.base.db.App" canDelete="false" canNew="false">
|
||||
<field name="name" />
|
||||
<field name="active" />
|
||||
<field name="demoDataLoaded" />
|
||||
</grid>
|
||||
|
||||
<form name="app-form" title="App" model="com.axelor.apps.base.db.App" canDelete="false" canNew="false" width="large">
|
||||
<panel name="mainPanel">
|
||||
<field name="image" widget="Image" colSpan="2"/>
|
||||
<panel colSpan="6" name="infoPanel">
|
||||
<field name="name" readonly="true" colSpan="12" />
|
||||
<field name="languageSelect" selection-in="['en','fr']"/>
|
||||
<field name="demoDataLoaded" readonly="true" if="com.axelor.app.AppSettings.get().get('application.mode') != 'prod'"/>
|
||||
</panel>
|
||||
<field name="description" colSpan="12" readonly="true"/>
|
||||
<field name="initDataLoaded" hidden="true" />
|
||||
<field name="code" hidden="true" />
|
||||
<field name="active" hidden="true" />
|
||||
<field name="isRolesImported" hidden="true" />
|
||||
</panel>
|
||||
<panel itemSpan="12" sidebar="true" name="actionPanel">
|
||||
<button name="installBtn" title="Install" onClick="action-app-alert-install,save,action-app-method-install-app" showIf="!active" />
|
||||
<button name="uninstallBtn" title="Uninstall" onClick="action-app-alert-un-install,action-app-method-uninstall-app" showIf="active" />
|
||||
<button name="configureBtn" title="Configure" showIf="active" onClick="com.axelor.apps.base.web.AppController:configure" />
|
||||
<button name="importDataDemoBtn" if="com.axelor.app.AppSettings.get().get('application.mode') != 'prod'" showIf="active && !demoDataLoaded" onClick="action-app-alert-demo-import,save,action-method-app-import-data-demo" title="Import demo data" />
|
||||
<button name="importRolesBtn" showIf="active && !isRolesImported" onClick="save,action-app-method-import-roles" title="Import roles" />
|
||||
</panel>
|
||||
<panel-related name="dependsOnSetPanel" field="dependsOnSet" readonly="true" canSelect="false" canView="false" >
|
||||
<field name="name" />
|
||||
<field name="active"/>
|
||||
</panel-related>
|
||||
</form>
|
||||
|
||||
<form model="com.axelor.apps.base.db.App" title="Bulk install" name="bulk-install-app-form">
|
||||
<panel name="mainPanel">
|
||||
<button name="selectAllBtn" title="Select all" colSpan="4" onClick="action-app-bulk-install-select-all,action-app-bulk-install-apps-set-change"/>
|
||||
<field type="many-to-many" name="appsSet" target="com.axelor.apps.base.db.App" colSpan="12" title="Apps" canNew="false" domain="self.active = false or self.demoDataLoaded = false" height="200" onChange="action-app-bulk-install-apps-set-change"/>
|
||||
<field name="languageSelect" type="string" selection="select.language" title="Language" colSpan="4" selection-in="['en','fr']"/>
|
||||
<field type="boolean" name="importDemoData" title="Import demo data" colSpan="3" if="com.axelor.app.AppSettings.get().get('application.mode') != 'prod'"/>
|
||||
<button name="installBtn" title="Install" colSpan="4" onClick="action-app-method-bulk-install" hidden="true"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<form model="com.axelor.apps.base.db.Wizard" title="Import access config" name="import-access-config-wizard-form">
|
||||
<panel name="wizardPanel">
|
||||
<field type="many-to-one" name="metaFile" title="File" widget="binary-link" target="com.axelor.meta.db.MetaFile"/>
|
||||
<button name="importAccessBtn" title="Import" colSpan="4" onClick="action-app-method-import-access-config" readonlyIf="metaFile == null"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<cards name="app-cards" model="com.axelor.apps.base.db.App" title="Apps" css="rect-image" orderBy="sequence" width="25%:200" >
|
||||
<toolbar>
|
||||
<button name="bulkInstallBtn" title="Bulk install" onClick="action-app-open-bulk-install-selector"/>
|
||||
<button name="refreshAppBtn" title="Refresh Apps" onClick="action-app-method-refresh-app"/>
|
||||
<button name="importRolesBtn" title="Import roles" onClick="action-app-method-import-all-roles" />
|
||||
</toolbar>
|
||||
<field name="name" />
|
||||
<field name="code" x-bind="{{code|unaccent|uppercase}}" />
|
||||
<field name="image" />
|
||||
<field name="description"/>
|
||||
<field name="active"/>
|
||||
<field name="sequence"/>
|
||||
<template><![CDATA[
|
||||
<div>
|
||||
<div class="span4 card-image">
|
||||
<img ng-src="{{$image('image', 'content')}}" />
|
||||
</div>
|
||||
<div class="span8">
|
||||
<strong x-translate>{{$fmt('name')}}</strong>
|
||||
</div>
|
||||
<div class="span12">
|
||||
<a class="btn btn-small btn-info pull-right" href="" ng-show="!active" ui-action-click="action-app-alert-install,action-app-method-install-app" x-translate>Install</a>
|
||||
<a class="btn btn-small btn-success pull-right" href="" ng-show="active" ui-action-click="com.axelor.apps.base.web.AppController:configure" x-translate>Configure</a>
|
||||
<a class="btn btn-small btn-danger pull-right" href="" ng-show="active" ui-action-click="action-app-alert-un-install,action-app-method-uninstall-app" x-translate>Uninstall</a>
|
||||
</div>
|
||||
</div>
|
||||
]]>
|
||||
</template>
|
||||
</cards>
|
||||
|
||||
<action-record name="action-app-bulk-install-select-all" model="com.axelor.apps.base.db.App">
|
||||
<field name="appsSet" expr="eval:(__repo__(App).all().filter('self.active = false or self.demoDataLoaded = false').fetch()).toSet()"/>
|
||||
</action-record>
|
||||
|
||||
<action-method name="action-method-app-import-data-demo">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="importDataDemo"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-install-app">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="installApp" />
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-uninstall-app">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="uninstallApp" />
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-bulk-install" >
|
||||
<call class="com.axelor.apps.base.web.AppController" method="bulkInstall" />
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-refresh-app">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="refreshApp"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-import-roles">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="importRoles"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-import-all-roles">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="importAllRoles"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-generate-access-template">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="generateAccessTemplate"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-app-method-import-access-config">
|
||||
<call class="com.axelor.apps.base.web.AppController" method="importAccessConfig"/>
|
||||
</action-method>
|
||||
|
||||
<action-view name="action-app-open-bulk-install-selector" title="Bulk install" model="com.axelor.apps.base.db.App">
|
||||
<view type="form" name="bulk-install-app-form"/>
|
||||
<view-param name="show-toolbar" value="false"/>
|
||||
<view-param name="popup" value="true" />
|
||||
<view-param name="popup-save" value="false" />
|
||||
<view-param name="show-confirm" value="false" />
|
||||
<view-param name="height" value="400" />
|
||||
</action-view>
|
||||
|
||||
<action-view name="action-app-view-import-access-config-wizard" title="Import access config" model="com.axelor.apps.base.db.Wizard">
|
||||
<view type="form" name="import-access-config-wizard-form"/>
|
||||
<view-param name="show-toolbar" value="false"/>
|
||||
<view-param name="popup" value="true" />
|
||||
<view-param name="popup-save" value="false" />
|
||||
<view-param name="show-confirm" value="false" />
|
||||
</action-view>
|
||||
|
||||
<action-attrs name="action-app-on-load-attrs">
|
||||
<attribute name="hidden" for="configureBtn" expr="eval:!__self__.active"/>
|
||||
<attribute name="hidden" for="demoDataLoaded" expr="eval:!__self__.active"/>
|
||||
<attribute name="hidden" for="importDataDemoBtn" expr="eval:!__self__.active || demoDataLoaded"/>
|
||||
</action-attrs>
|
||||
|
||||
<action-attrs name="action-app-bulk-install-apps-set-change">
|
||||
<attribute name="hidden" for="installBtn" expr="eval:appsSet == null || appsSet.empty"/>
|
||||
</action-attrs>
|
||||
|
||||
<action-validate name="action-app-alert-install">
|
||||
<alert message="Are you sure wants to install '${name}'?"/>
|
||||
</action-validate>
|
||||
|
||||
<action-validate name="action-app-alert-un-install">
|
||||
<alert message="Are you sure wants to uninstall '${name}'?"/>
|
||||
</action-validate>
|
||||
|
||||
<action-validate name="action-app-alert-demo-import">
|
||||
<alert message="Are you sure wants to import demo data for '${name}'?"/>
|
||||
</action-validate>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid model="com.axelor.apps.base.db.DataBackup" title="Create Backup"
|
||||
name="data-create-backup-grid">
|
||||
<field name="statusSelect" />
|
||||
<field name="backupMetaFile" widget="binary-link" />
|
||||
<field name="backupDate" />
|
||||
</grid>
|
||||
|
||||
<form model="com.axelor.apps.base.db.DataBackup" title="Create Backup"
|
||||
name="data-create-backup-form" onNew="data.create.backup.load.today.date"
|
||||
width="large">
|
||||
<panel name="mainPanel">
|
||||
<field name="statusSelect" readonly="true" widget="NavSelect"
|
||||
selection-in="[0,1,2]" colSpan="12" />
|
||||
<field name="fetchLimit" widget="Integer" required="true" min="1"
|
||||
colSpan="4" />
|
||||
<field name="isRelativeDate" widget="boolean-switch" onChange="action-data-backup-attrs-onchange-relativedate" colSpan="2"/>
|
||||
<field name="updateImportId" widget="boolean-switch" showIf="isRelativeDate" colSpan="2"/>
|
||||
<spacer colSpan="4"/>
|
||||
<button-group name="mainBtnGroup" colSpan="3">
|
||||
<button name="backToDraftBtn" title="Return to draft status"
|
||||
showIf="backupMetaFile != null && statusSelect == 1"
|
||||
colSpan="4" onClick="data.create.backup.set.draft.status" />
|
||||
<button name="createBtn" title="Create"
|
||||
showIf="backupMetaFile == null && statusSelect != 1"
|
||||
colSpan="4" onClick="save,action.data.backup.call.createbackup,save" />
|
||||
</button-group>
|
||||
<spacer name="btnGrpSpacer" colSpan="12"/>
|
||||
<field name="backupMetaFile" readonly="true" widget="binary-link"
|
||||
colSpan="4" />
|
||||
<field name="backupDate" readonly="true" colSpan="4" />
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<grid model="com.axelor.apps.base.db.DataBackup" title="Restore Backup"
|
||||
name="data-restore-backup-grid">
|
||||
<field name="statusSelect" />
|
||||
<field name="backupMetaFile" widget="binary-link" />
|
||||
<field name="restoreDate" />
|
||||
</grid>
|
||||
|
||||
<form model="com.axelor.apps.base.db.DataBackup" title="Restore Backup"
|
||||
name="data-restore-backup-form" width="mid"
|
||||
onNew="data.restore.backup.load.today.date">
|
||||
<panel>
|
||||
<field name="statusSelect" selection-in="[0,1,3,4]" readonly="true"
|
||||
widget="NavSelect" colSpan="12" />
|
||||
<field name="backupMetaFile" required="true"
|
||||
domain="self.fileName LIKE '%.zip'" colSpan="3" widget="binary-link"/>
|
||||
<button name="restoreBtn" title="Restore"
|
||||
showIf="backupMetaFile != null && statusSelect != 2"
|
||||
onClick="save,action.data.backup.call.restorebackup,save" colSpan="3" />
|
||||
<spacer name="restoreBtnSpacer" />
|
||||
<field name="restoreDate" readonly="true" colSpan="3" />
|
||||
<field name="logMetaFile" readonly="true" widget="binary-link"
|
||||
colSpan="3" />
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-record name="data.create.backup.load.today.date"
|
||||
model="com.axelor.apps.base.db.DataBackup">
|
||||
<field name="backupDate" expr="eval: __datetime__" />
|
||||
<field name="fetchLimit" expr="100" />
|
||||
</action-record>
|
||||
|
||||
<action-record name="data.restore.backup.load.today.date"
|
||||
model="com.axelor.apps.base.db.DataBackup">
|
||||
<field name="restoreDate" expr="eval: __datetime__" />
|
||||
</action-record>
|
||||
|
||||
<action-record name="data.create.backup.set.draft.status"
|
||||
model="com.axelor.apps.base.db.DataBackup">
|
||||
<field name="statusSelect" expr="0" />
|
||||
<field name="backupMetaFile" expr="" />
|
||||
</action-record>
|
||||
|
||||
<action-method name="action.data.backup.call.createbackup">
|
||||
<call class="com.axelor.apps.base.web.DataBackupController"
|
||||
method="CreateBackUp" />
|
||||
</action-method>
|
||||
|
||||
<action-method name="action.data.backup.call.restorebackup">
|
||||
<call class="com.axelor.apps.base.web.DataBackupController"
|
||||
method="RestoreBackUp" />
|
||||
</action-method>
|
||||
|
||||
<action-attrs name="action-data-backup-attrs-onchange-relativedate">
|
||||
<attribute name="value" for="updateImportId" expr="false" if="!isRelativeDate"/>
|
||||
</action-attrs>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="data-config-line-grid" title="Data config line" model="com.axelor.apps.base.db.DataConfigLine">
|
||||
<field name="metaModel" />
|
||||
<field name="path" required="true" />
|
||||
<field name="metaFieldPath"/>
|
||||
</grid>
|
||||
|
||||
<form name="data-config-line-form" title="Data config line"
|
||||
model="com.axelor.apps.base.db.DataConfigLine" width="large">
|
||||
<panel name="mainPanel">
|
||||
<field name="metaModel" canEdit="false" />
|
||||
<field name="tabName" />
|
||||
<field name="typeSelect"/>
|
||||
<field name="metaFieldPath" hidden="true" showIf="typeSelect == 0" requiredIf="typeSelect == 0" onSelect="action-object-attrs-metaFieldPath-onselect"/>
|
||||
<field name="path" hidden="true" showIf="typeSelect == 1" requiredIf="typeSelect == 1" colSpan="12"/>
|
||||
<panel-related name="toExportMetaFieldSetPanel" field="toExportMetaFieldSet" canNew="false" canEdit="false" colSpan="12"
|
||||
domain="self.metaModel = :metaModel and (self.relationship is null or self.relationship not in ('ManyToMany','OneToMany'))">
|
||||
<field name="name" />
|
||||
<field name="label" />
|
||||
<field name="typeName" />
|
||||
</panel-related>
|
||||
<field name="resetPathSelect" showIf="typeSelect == 0" onChange="action-data-config-line-record-set-record-model"/>
|
||||
<field name="record" widget="RefSelect" x-related="recordSelectId" selection-in="null" showIf="resetPathSelect == 1 && typeSelect == 0"/>
|
||||
<field name="recordSelectId" hidden="true" />
|
||||
<panel-related name="toDeleteMetaFieldSetsPanel" field="toDeleteMetaFieldSet" canNew="false" canEdit="false" colSpan="12"
|
||||
domain="self in (:toExportMetaFieldSet)">
|
||||
<field name="name" />
|
||||
<field name="label" />
|
||||
<field name="typeName" />
|
||||
</panel-related>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-record name="action-data-config-line-record-set-record-model" model="com.axelor.apps.base.db.DataConfigLine">
|
||||
<field name="record" expr="eval: _parent?.modelSelect" if="resetPathSelect == 1 && typeSelect == 0"/>
|
||||
<field name="recordSelectId" expr="0" if="resetPathSelect == 1 && typeSelect == 0"/>
|
||||
<field name="record" expr="eval: null" if="resetPathSelect != 1 || typeSelect != 0"/>
|
||||
</action-record>
|
||||
|
||||
<action-attrs name="action-object-attrs-metaFieldPath-onselect">
|
||||
<attribute name="domain" expr="eval:" self.metaModel = :metaModel AND CONCAT(self.packageName ,'.',self.typeName) = '${_parent?.modelSelect}'"" for="metaFieldPath"/>
|
||||
</action-attrs>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<menuitem name="menu-auth-permission-assistant" parent="menu-auth" title="Permission Assistant"
|
||||
action="action-auth-permission-assistant"/>
|
||||
|
||||
<action-view name="action-auth-permission-assistant" title="Permission Assistant"
|
||||
model="com.axelor.auth.db.PermissionAssistant">
|
||||
<view type="grid" name="permission-assistant-grid" />
|
||||
<view type="form" name="permission-assistant-form" />
|
||||
</action-view>
|
||||
|
||||
<menuitem name="menu-meta-group-menu-assistant" parent="menu-auth" title="Group Menu Assistant"
|
||||
action="action-group-menu-assistant" />
|
||||
|
||||
<action-view name="action-group-menu-assistant" title="Group Menu Assistant"
|
||||
model="com.axelor.meta.db.MetaGroupMenuAssistant">
|
||||
<view type="grid" name="meta-group-menu-assistant-grid" />
|
||||
<view type="form" name="meta-group-menu-assistant-form" />
|
||||
</action-view>
|
||||
|
||||
<menuitem name="menu-meta-permission" parent="menu-auth" title="Meta Permissions"
|
||||
action="action-meta-permission" />
|
||||
|
||||
<action-view name="action-meta-permission" title="Meta Permissions"
|
||||
model="com.axelor.meta.db.MetaPermission">
|
||||
<view type="grid" name="meta-permission-grid" />
|
||||
<view type="form" name="meta-permission-form" />
|
||||
</action-view>
|
||||
|
||||
<menuitem name="admin-root" title="Application Config" order="-60"
|
||||
icon="fa-gear" icon-background="#31b4a9"/>
|
||||
|
||||
<menuitem name="admin-root-app-management" parent="admin-root" order="5"
|
||||
title="Apps management" action="admin.root.app.management" />
|
||||
|
||||
<action-view name="admin.root.app.management" title="Apps management"
|
||||
model="com.axelor.apps.base.db.App" >
|
||||
<view type="cards" name="app-cards"/>
|
||||
<view type="grid" name="app-grid"/>
|
||||
<view type="form" name="app-form"/>
|
||||
</action-view>
|
||||
|
||||
<menuitem name="admin-root-maintenance" parent="admin-root" if="__config__.app.isApp('base')"
|
||||
title="Technical maintenance" order="45" />
|
||||
|
||||
<menuitem name="admin-root-object-data-config" parent="admin-root-maintenance"
|
||||
title="Object data config" action="admin.root.object.data.config" order="10"/>
|
||||
|
||||
<action-view name="admin.root.object.data.config" title="Object data config"
|
||||
model="com.axelor.apps.base.db.ObjectDataConfig" >
|
||||
<view type="grid" name="object-data-config-grid"/>
|
||||
<view type="form" name="object-data-config-form"/>
|
||||
</action-view>
|
||||
|
||||
<menuitem name="admin-root-data-backup" parent="admin-root-maintenance" title="Backup" order="11"/>
|
||||
|
||||
<menuitem name="admin-root-data-create-backup" parent="admin-root-data-backup"
|
||||
title="Create Backup" action="admin.root.data.create.backup"/>
|
||||
|
||||
<action-view name="admin.root.data.create.backup" title="Create Backup"
|
||||
model="com.axelor.apps.base.db.DataBackup" >
|
||||
<view type="grid" name="data-create-backup-grid"/>
|
||||
<view type="form" name="data-create-backup-form"/>
|
||||
<domain>self.backupDate != null</domain>
|
||||
</action-view>
|
||||
|
||||
<menuitem name="admin-root-data-restore-backup" parent="admin-root-data-backup"
|
||||
title="Restore Backup" action="admin.root.data.restore.backup"/>
|
||||
|
||||
<action-view name="admin.root.data.restore.backup" title="Restore Backup"
|
||||
model="com.axelor.apps.base.db.DataBackup" >
|
||||
<view type="grid" name="data-restore-backup-grid"/>
|
||||
<view type="form" name="data-restore-backup-form"/>
|
||||
<domain>self.restoreDate != null</domain>
|
||||
</action-view>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid title="Group Menu Assistant" name="meta-group-menu-assistant-grid" model="com.axelor.meta.db.MetaGroupMenuAssistant">
|
||||
<field name="metaFile" />
|
||||
<field name="importDate" />
|
||||
<field name="createdBy" />
|
||||
<field name="updatedBy" />
|
||||
</grid>
|
||||
|
||||
<form title="Group Menu Assistant" name="meta-group-menu-assistant-form" model="com.axelor.meta.db.MetaGroupMenuAssistant" width="large">
|
||||
<panel name="mainPanel" >
|
||||
<panel-related name="groupSetPanel" field="groupSet" colSpan="6" />
|
||||
<panel-related name="roleSetPanel" field="roleSet" colSpan="6" />
|
||||
<panel-related name="menuSetPanel" field="menuSet" grid-view="simple-meta-menu-grid" colSpan="12"/>
|
||||
<field name="metaFile" colSpan="6" widget="binary-link" />
|
||||
<field name="language" readonlyIf="metaFile"/>
|
||||
<button name="createFileBtn" title="Create file" colSpan="3" onClick="save,action-meta-method-create-group-menu-file"/>
|
||||
<button name="importFileBtn" title="Import" readonlyIf="metaFile == null" colSpan="3" onClick="save,action-meta-method-import-group-menu,save"/>
|
||||
<panel name="detailsPanel" title="Details" colSpan="12">
|
||||
<field name="importDate" colSpan="4"/>
|
||||
<field name="createdBy" colSpan="4"/>
|
||||
<field name="updatedBy" colSpan="4"/>
|
||||
</panel>
|
||||
<field name="log" colSpan="12"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-method name="action-meta-method-create-group-menu-file">
|
||||
<call class="com.axelor.meta.web.MetaGroupMenuAssistantController" method="createGroupMenuFile"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-meta-method-import-group-menu">
|
||||
<call class="com.axelor.meta.web.MetaGroupMenuAssistantController" method="importGroupMenu"/>
|
||||
</action-method>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="object-data-config-grid" title="Object data config" model="com.axelor.apps.base.db.ObjectDataConfig">
|
||||
<field name="title"/>
|
||||
<field name="modelSelect"/>
|
||||
</grid>
|
||||
|
||||
<form name="object-data-config-form" title="Object data config" model="com.axelor.apps.base.db.ObjectDataConfig"
|
||||
onNew="action-object-data-config-record-on-onnew">
|
||||
<panel name="overviewPanel">
|
||||
<field name="statusSelect" showTitle="false" readonly="true" colSpan="12" widget="NavSelect"/>
|
||||
<panel name="mainPanel" colSpan="12">
|
||||
<field name="title"/>
|
||||
<field name="modelSelect"/>
|
||||
</panel>
|
||||
</panel>
|
||||
<panel name="contentPanel" colSpan="12">
|
||||
<panel-related name="dataConfigLineListPanel" field="dataConfigLineList" colSpan="12" />
|
||||
<field name="comment" widget="HTML" colSpan="12"/>
|
||||
</panel>
|
||||
<panel-dashlet name="exportByObject" title="Exports" action="action-object-data-config-dashlet-export-by-object" colSpan="12"/>
|
||||
<panel name="actionPanel" sidebar="true">
|
||||
<button name="exportBtn" title="Export" onClick="save,action-open-object-data-export-form"/>
|
||||
<button name="anonymizeBtn" title="Anonymize" onClick="save,action-open-object-data-anonymize-wizard"/>
|
||||
<button name="draftBtn" title="Draft" showIf="statusSelect == 3" onClick="action-object-data-config-record-set-status,save"/>
|
||||
<button name="validatedBtn" title="Validated" showIf="statusSelect == 1" onClick="save,action-object-data-config-record-set-status,save"/>
|
||||
<button name="cancelledBtn" title="Cancelled" showIf="statusSelect == 2" onClick="save,action-object-data-config-record-set-status,save"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-record name="action-object-data-config-record-on-onnew" model="com.axelor.apps.base.db.ObjectDataConfig">
|
||||
<field name="statusSelect" expr="1"/>
|
||||
</action-record>
|
||||
|
||||
<action-record name="action-object-data-config-record-set-status" model="com.axelor.apps.base.db.ObjectDataConfig">
|
||||
<field name="statusSelect" expr="1" if="statusSelect == 3"/>
|
||||
<field name="statusSelect" expr="2" if="statusSelect == 1"/>
|
||||
<field name="statusSelect" expr="3" if="statusSelect == 2"/>
|
||||
</action-record>
|
||||
|
||||
<action-view name="action-open-object-data-export-form" title="Export" model="com.axelor.apps.base.db.ObjectDataConfigExport">
|
||||
<view type="form" name="object-data-config-export-form" />
|
||||
<view-param name="show-toolbar" value="false"/>
|
||||
<view-param name="show-confirm" value="false"/>
|
||||
<view-param name="popup" value="reload"/>
|
||||
<view-param name="popup-save" value="false"/>
|
||||
<context name="_objectDataConfig" expr="eval:__this__" />
|
||||
</action-view>
|
||||
|
||||
<action-view name="action-open-object-data-anonymize-wizard" title="Anonymize" model="com.axelor.apps.base.db.Wizard">
|
||||
<view type="form" name="object-data-anonymize-wizard-form" />
|
||||
<view-param name="show-toolbar" value="false"/>
|
||||
<view-param name="show-confirm" value="false"/>
|
||||
<view-param name="popup" value="true"/>
|
||||
<view-param name="popup-save" value="false"/>
|
||||
<context name="_modelSelect" expr="eval:modelSelect"/>
|
||||
<context name="_objectDataConfigId" expr="eval:id" />
|
||||
</action-view>
|
||||
|
||||
<action-view name="action-object-data-config-dashlet-export-by-object" title="Exports" model="com.axelor.apps.base.db.ObjectDataConfigExport">
|
||||
<view type="grid" name="object-data-config-export-grid"/>
|
||||
<view type="form" name="object-data-config-export-form"/>
|
||||
<domain>self.objectDataConfig.id = :id</domain>
|
||||
<context name="id" expr="eval: id"/>
|
||||
</action-view>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="object-data-config-export-grid" title="Object data config exports" model="com.axelor.apps.base.db.ObjectDataConfigExport">
|
||||
<field name="recordName"/>
|
||||
<field name="createdOn"/>
|
||||
<field name="exportFormatSelect"/>
|
||||
</grid>
|
||||
|
||||
<form name="object-data-config-export-form" title="Object data config export" model="com.axelor.apps.base.db.ObjectDataConfigExport"
|
||||
onNew="action-object-data-config-export-record-on-onnew">
|
||||
<panel name="mainPanel">
|
||||
<field name="modelSelect" title="Model" widget="RefSelect" x-related="modelSelectId" selection-in="null" />
|
||||
<field name="exportFormatSelect" title="Export format" selection="object.data.export.format.select" type="string"/>
|
||||
<field name="langSelect" title="Language" selection="select.language" type="string" />
|
||||
<spacer name="langSelectSpacer"/>
|
||||
<button name="exportBtn" title="Export" onClick="save,action-object-data-export-method-export" readonlyIf="modelSelectId == 0" />
|
||||
<field name="modelSelectId" hidden="true" />
|
||||
<field name="objectDataConfig" hidden="true"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-record name="action-object-data-config-export-record-on-onnew" model="com.axelor.apps.base.db.ObjectDataConfigExport">
|
||||
<field name="modelSelect" expr="eval:_objectDataConfig?.modelSelect"/>
|
||||
<field name="langSelect" expr="eval:__user__.language"/>
|
||||
<field name="objectDataConfig" expr="eval:_objectDataConfig"/>
|
||||
<field name="exportFormatSelect" expr="eval:'csv'"/>
|
||||
</action-record>
|
||||
|
||||
<action-method name="action-object-data-export-method-export">
|
||||
<call class="com.axelor.apps.base.web.ObjectDataExportController" method="export"/>
|
||||
</action-method>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<form name="object-data-anonymize-wizard-form" title="Object data anonymize" model="com.axelor.apps.base.db.Wizard"
|
||||
onNew="action-object-data-anonymize-wizard-defaults">
|
||||
<panel name="mainPanel">
|
||||
<field name="modelSelect" title="Model" widget="RefSelect" x-related="modelSelectId" selection="object.data.config.model.select" selection-in="null" type="string" required="true"/>
|
||||
<button name="anonymizeBtn" title="Anonymize data" onClick="action-object-data-export-wizard-validate-anonymize,action-object-data-export-wizard-method-anonymyize" readonlyIf="modelSelectId == 0" />
|
||||
<field name="modelSelectId" hidden="true" />
|
||||
<field name="objectDataConfigId" hidden="true"/>
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-attrs name="action-object-data-anonymize-wizard-defaults">
|
||||
<attribute name="value" for="modelSelect" expr="eval:_modelSelect"/>
|
||||
<attribute name="value" for="objectDataConfigId" expr="eval:_objectDataConfigId" />
|
||||
</action-attrs>
|
||||
|
||||
<action-validate name="action-object-data-export-wizard-validate-anonymize">
|
||||
<alert message="Are you sure wants to anonymize data ?."/>
|
||||
</action-validate>
|
||||
|
||||
<action-method name="action-object-data-export-wizard-method-anonymyize">
|
||||
<call class="com.axelor.apps.base.web.ObjectDataExportController" method="anonymize"/>
|
||||
</action-method>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid title="Permission Assistant" name="permission-assistant-grid" model="com.axelor.auth.db.PermissionAssistant">
|
||||
<field name="metaFile" />
|
||||
<field name="importDate" />
|
||||
<field name="createdBy" />
|
||||
<field name="updatedBy" />
|
||||
</grid>
|
||||
|
||||
<form name="permission-assistant-form" title="Permission Assistant" model="com.axelor.auth.db.PermissionAssistant">
|
||||
<panel name="mainPanel" >
|
||||
<field name="typeSelect" selection="permission.assistant.type.select" onChange="action-attr-reset-all-set"/>
|
||||
<panel-related name="roleSetPanel" field="roleSet" showIf="typeSelect == 2" colSpan="12"/>
|
||||
<panel-related name="groupSetPanel" field="groupSet" showIf="typeSelect == 1" colSpan="12"/>
|
||||
<panel-related name="objectSetPanel" field="objectSet" colSpan="12"/>
|
||||
<field name="metaField" domain="self.metaModel.name = 'User' and self.relationship != null" onChange="action-permission-assistant-method-fill-objects"/>
|
||||
<field name="fieldPermission" />
|
||||
<field name="metaFile" colSpan="12" widget="binary-link" />
|
||||
<field name="language" />
|
||||
<button name="createFileBtn" title="Create file" colSpan="3" onClick="save,action-permission-method-create-file"/>
|
||||
<button name="importPermissionsBtn" title="Import permissions" readonlyIf="metaFile == null" colSpan="3" onClick="save,action-permission-method-import-permissions,save"/>
|
||||
<panel name="detailsPanel" title="Details" readonly="true" colSpan="12">
|
||||
<field name="importDate" colSpan="4"/>
|
||||
<field name="createdBy" colSpan="4"/>
|
||||
<field name="updatedBy" colSpan="4"/>
|
||||
</panel>
|
||||
<field name="log" colSpan="12" readonly="true" />
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
<action-method name="action-permission-method-create-file" model="com.axelor.auth.db.PermissionAssistant">
|
||||
<call class="com.axelor.auth.web.PermissionAssistantController" method="createFile"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-permission-method-import-permissions" model="com.axelor.auth.db.PermissionAssistant">
|
||||
<call class="com.axelor.auth.web.PermissionAssistantController" method="importPermissions"/>
|
||||
</action-method>
|
||||
|
||||
<action-method name="action-permission-assistant-method-fill-objects">
|
||||
<call class="com.axelor.auth.web.PermissionAssistantController" method="fillObjects"/>
|
||||
</action-method>
|
||||
|
||||
<action-attrs name="action-attr-reset-all-set">
|
||||
<attribute name="value" for="groupSet" expr="eval: null" if="typeSelect == 2"/>
|
||||
<attribute name="value" for="roleSet" expr="eval: null" if="typeSelect == 1"/>
|
||||
</action-attrs>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<selection name="permission.assistant.type.select">
|
||||
<option value="1">Groups</option>
|
||||
<option value="2">Roles</option>
|
||||
</selection>
|
||||
|
||||
<selection name="object.data.config.model.select">
|
||||
<option value="com.axelor.auth.db.User">User</option>
|
||||
</selection>
|
||||
|
||||
<selection name="object.data.config.status.select">
|
||||
<option value="1">Draft</option>
|
||||
<option value="2">Validated</option>
|
||||
<option value="3">Cancelled</option>
|
||||
</selection>
|
||||
|
||||
<selection name="object.data.export.format.select">
|
||||
<option value="csv">CSV</option>
|
||||
<option value="xlsx">Excel</option>
|
||||
</selection>
|
||||
|
||||
<selection name="data.config.line.type.select">
|
||||
<option value="0">Path</option>
|
||||
<option value="1">Query</option>
|
||||
</selection>
|
||||
|
||||
<selection name="data.config.line.reset.path.select">
|
||||
<option value="0">None</option>
|
||||
<option value="1">Replace</option>
|
||||
<option value="2">Delete</option>
|
||||
</selection>
|
||||
|
||||
<selection name="data.backup.status.select">
|
||||
<option value="0">Draft</option>
|
||||
<option value="1">In Progress</option>
|
||||
<option value="2">Backup Created</option>
|
||||
<option value="3">Backup Restored</option>
|
||||
<option value="4">Backup Restore Error</option>
|
||||
</selection>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<object-views xmlns="http://axelor.com/xml/ns/object-views"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://axelor.com/xml/ns/object-views http://axelor.com/xml/ns/object-views/object-views_5.2.xsd">
|
||||
|
||||
<grid name="user-access-config-grid" title="User access config" model="com.axelor.apps.base.db.UserAccessConfig">
|
||||
<field name="app" />
|
||||
<field name="accessConfig" domain="self.app = :app" />
|
||||
</grid>
|
||||
|
||||
<form name="user-access-config-form" title="User access config" model="com.axelor.apps.base.db.UserAccessConfig" editable="true">
|
||||
<panel name="mainPanel">
|
||||
<field name="app" />
|
||||
<field name="accessConfig" domain="self.app = :app" />
|
||||
</panel>
|
||||
</form>
|
||||
|
||||
</object-views>
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.user-preferences .form-view.bar-layout.mini-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
Reference in New Issue
Block a user