Merge branch 'mod/presupuesto_cliente_and_cancel_pedido' into 'main'

Mod/presupuesto cliente and cancel pedido

See merge request jjimenez/erp-imprimelibros!26
This commit is contained in:
2025-12-29 20:29:39 +00:00
18 changed files with 8235 additions and 70 deletions

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,12 @@
<liquibase.version>4.29.2</liquibase.version> <liquibase.version>4.29.2</liquibase.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>

View File

@ -16,6 +16,7 @@ import java.util.Objects;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter; import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import com.imprimelibros.erp.presupuesto.service.PresupuestoService; import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
import com.imprimelibros.erp.users.UserService;
import com.imprimelibros.erp.cart.dto.CartDireccionRepository; import com.imprimelibros.erp.cart.dto.CartDireccionRepository;
import com.imprimelibros.erp.cart.dto.DireccionCardDTO; import com.imprimelibros.erp.cart.dto.DireccionCardDTO;
import com.imprimelibros.erp.cart.dto.DireccionShipment; import com.imprimelibros.erp.cart.dto.DireccionShipment;
@ -41,11 +42,12 @@ public class CartService {
private final skApiClient skApiClient; private final skApiClient skApiClient;
private final PresupuestoService presupuestoService; private final PresupuestoService presupuestoService;
private final PedidoRepository pedidoRepository; private final PedidoRepository pedidoRepository;
private final UserService userService;
public CartService(CartRepository cartRepo, CartItemRepository itemRepo, public CartService(CartRepository cartRepo, CartItemRepository itemRepo,
CartDireccionRepository cartDireccionRepo, MessageSource messageSource, CartDireccionRepository cartDireccionRepo, MessageSource messageSource,
PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, PedidoRepository pedidoRepository, PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, PedidoRepository pedidoRepository,
DireccionService direccionService, skApiClient skApiClient,PresupuestoService presupuestoService, EmailService emailService) { DireccionService direccionService, skApiClient skApiClient,PresupuestoService presupuestoService, EmailService emailService, UserService userService) {
this.cartRepo = cartRepo; this.cartRepo = cartRepo;
this.itemRepo = itemRepo; this.itemRepo = itemRepo;
this.cartDireccionRepo = cartDireccionRepo; this.cartDireccionRepo = cartDireccionRepo;
@ -56,6 +58,7 @@ public class CartService {
this.presupuestoService = presupuestoService; this.presupuestoService = presupuestoService;
this.emailService = emailService; this.emailService = emailService;
this.pedidoRepository = pedidoRepository; this.pedidoRepository = pedidoRepository;
this.userService = userService;
} }
public Cart findById(Long cartId) { public Cart findById(Long cartId) {
@ -420,6 +423,13 @@ public class CartService {
cart.setUserId(customerId); cart.setUserId(customerId);
cartRepo.save(cart); cartRepo.save(cart);
// Se mueven los presupuestos de cartitems a ese usuario
List<CartItem> items = itemRepo.findByCartId(cart.getId());
for (CartItem item : items) {
Presupuesto p = item.getPresupuesto();
p.setUser(userService.findById(customerId));
presupuestoRepo.save(p);
}
return true; return true;
} catch (Exception e) { } catch (Exception e) {

View File

@ -576,6 +576,42 @@ public class skApiClient {
return Boolean.parseBoolean(result); return Boolean.parseBoolean(result);
} }
public Boolean cancelarPedido(Long pedidoId) {
String result = performWithRetry(() -> {
String url = this.skApiUrl + "api/cancelar-pedido/" + pedidoId;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authService.getToken());
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class);
try {
Map<String, Object> responseBody = new ObjectMapper().readValue(
response.getBody(),
new TypeReference<Map<String, Object>>() {
});
Boolean success = (Boolean) (responseBody.get("success") != null ? responseBody.get("success") : false);
return success.toString();
} catch (JsonProcessingException e) {
e.printStackTrace();
return "false"; // Fallback en caso de error
}
});
return Boolean.parseBoolean(result);
}
/****************** /******************
* PRIVATE METHODS * PRIVATE METHODS
******************/ ******************/

View File

@ -86,15 +86,34 @@ public class PedidoService {
} }
// Auditoría mínima // Auditoría mínima
Long userId = cart.getUserId(); /*Long userId = cart.getUserId();
pedido.setCreatedBy(userService.findById(userId)); pedido.setCreatedBy(userService.findById(userId));
pedido.setUpdatedBy(userService.findById(userId));
*/
// Se obtiene el usuario del primer presupuesto del carrito
Long userId = null;
List<CartItem> cartItems = cart.getItems();
if (!cartItems.isEmpty()) {
Presupuesto firstPresupuesto = cartItems.get(0).getPresupuesto();
if (firstPresupuesto != null) {
userId = firstPresupuesto.getUser().getId();
}
}
if(userId == null){
userId = cart.getUserId();
}
pedido.setCreatedBy(userService.findById(userId));
pedido.setUpdatedBy(userService.findById(userId));
pedido.setCreatedAt(Instant.now()); pedido.setCreatedAt(Instant.now());
pedido.setDeleted(false); pedido.setDeleted(false);
pedido.setUpdatedAt(Instant.now()); pedido.setUpdatedAt(Instant.now());
pedido.setUpdatedBy(userService.findById(userId));
// Guardamos el pedido // Guardamos el pedido
Pedido pedidoGuardado = pedidoRepository.save(pedido); Pedido pedidoGuardado = pedidoRepository.save(pedido);
pedidoGuardado.setCreatedBy(userService.findById(userId));
pedidoGuardado.setUpdatedBy(userService.findById(userId));
pedidoRepository.save(pedidoGuardado);
List<CartItem> items = cart.getItems(); List<CartItem> items = cart.getItems();
@ -336,9 +355,35 @@ public class PedidoService {
pedidoLineaRepository.save(pedidoLinea); pedidoLineaRepository.save(pedidoLinea);
return true; return true;
} }
public Boolean cancelarPedido(Long pedidoId) {
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if (pedido == null) {
return false;
}
Boolean result = skApiClient.cancelarPedido(Long.valueOf(pedido.getProveedorRef()));
if (!result) {
return false;
}
List<PedidoLinea> lineas = pedidoLineaRepository.findByPedidoId(pedidoId);
for (PedidoLinea linea : lineas) {
if (linea.getEstado() != PedidoLinea.Estado.terminado) {
linea.setEstado(PedidoLinea.Estado.cancelado);
pedidoLineaRepository.save(linea);
}
}
return true;
}
/*************************** /***************************
* MÉTODOS PRIVADOS * MÉTODOS PRIVADOS
***************************/ ***************************/
private byte[] downloadFile(Long pedidoLineaId, String fileType, Locale locale) { private byte[] downloadFile(Long pedidoLineaId, String fileType, Locale locale) {
PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null);
if (pedidoLinea == null) { if (pedidoLinea == null) {

View File

@ -211,6 +211,15 @@ public class PedidosController {
@PathVariable(name = "id", required = true) Long id, @PathVariable(name = "id", required = true) Long id,
Model model, Locale locale) { Model model, Locale locale) {
List<String> keys = List.of(
"app.cancelar",
"app.yes",
"pedido.view.cancel-title",
"pedido.view.cancel-text");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
Boolean isAdmin = Utils.isCurrentUserAdmin(); Boolean isAdmin = Utils.isCurrentUserAdmin();
if (isAdmin) { if (isAdmin) {
model.addAttribute("isAdmin", true); model.addAttribute("isAdmin", true);
@ -226,6 +235,7 @@ public class PedidosController {
model.addAttribute("direccionFacturacion", direccionFacturacion); model.addAttribute("direccionFacturacion", direccionFacturacion);
Boolean showCancel = false;
List<Map<String, Object>> lineas = pedidoService.getLineas(id, locale); List<Map<String, Object>> lineas = pedidoService.getLineas(id, locale);
for (Map<String, Object> linea : lineas) { for (Map<String, Object> linea : lineas) {
@ -252,6 +262,10 @@ public class PedidosController {
} }
linea.put("buttons", buttons); linea.put("buttons", buttons);
} }
if(pedidoLinea.getEstado() != PedidoLinea.Estado.cancelado && pedidoLinea.getEstado() != PedidoLinea.Estado.terminado) {
showCancel = true;
}
} }
List<PedidoDireccion> dirEntrega = pedidoService.getDireccionesEntregaPedidoLinea( List<PedidoDireccion> dirEntrega = pedidoService.getDireccionesEntregaPedidoLinea(
@ -267,10 +281,30 @@ public class PedidosController {
} }
model.addAttribute("lineas", lineas); model.addAttribute("lineas", lineas);
model.addAttribute("showCancel", showCancel);
model.addAttribute("id", id); model.addAttribute("id", id);
return "imprimelibros/pedidos/pedidos-view"; return "imprimelibros/pedidos/pedidos-view";
} }
@PostMapping("/cancel/{id}")
@ResponseBody
public Map<String, Object> cancelPedido(
@PathVariable(name = "id", required = true) Long id,
Locale locale) {
Boolean result = pedidoService.cancelarPedido(id);
if (result) {
String successMsg = messageSource.getMessage("pedido.success.pedido-cancelado", null, locale);
return Map.of(
"success", true,
"message", successMsg);
} else {
String errorMsg = messageSource.getMessage("pedido.errors.cancel-pedido", null, locale);
return Map.of(
"success", false,
"message", errorMsg);
}
}
// ------------------------------------- // -------------------------------------
// Acciones sobre las lineas de pedido // Acciones sobre las lineas de pedido
// ------------------------------------- // -------------------------------------

View File

@ -359,6 +359,7 @@ public class UserController {
@GetMapping(value = "api/get-users", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "api/get-users", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> getUsers( public Map<String, Object> getUsers(
@RequestParam(required = false) String role, // puede venir ausente @RequestParam(required = false) String role, // puede venir ausente
@RequestParam(required = false) Boolean showUsername,
@RequestParam(required = false) String q, @RequestParam(required = false) String q,
@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) { @RequestParam(defaultValue = "10") int size) {
@ -373,9 +374,15 @@ public class UserController {
.map(u -> { .map(u -> {
Map<String, Object> m = new HashMap<>(); Map<String, Object> m = new HashMap<>();
m.put("id", u.getId()); m.put("id", u.getId());
m.put("text", (u.getFullName() != null && !u.getFullName().isBlank()) if (showUsername != null && Boolean.TRUE.equals(showUsername)) {
? u.getFullName() m.put("text", (u.getFullName() != null && !u.getFullName().isBlank())
: u.getUserName()); ? u.getFullName() + " (" + u.getUserName() + ")"
: u.getUserName());
} else {
m.put("text", (u.getFullName() != null && !u.getFullName().isBlank())
? u.getFullName()
: u.getUserName());
}
return m; return m;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -385,4 +392,20 @@ public class UserController {
"pagination", Map.of("more", more)); "pagination", Map.of("more", more));
} }
@ResponseBody
@GetMapping(value = "api/get-user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> getUser(@PathVariable Long id) {
User u = userService.findById(id);
if (u == null) {
return Map.of();
}
Map<String, Object> m = new HashMap<>();
m.put("id", u.getId());
m.put("userName", u.getUserName());
m.put("fullName", u.getFullName());
return m;
}
} }

View File

@ -56,11 +56,16 @@ pedido.view.aceptar-ferro=Aceptar ferro
pedido.view.ferro-download=Descargar ferro pedido.view.ferro-download=Descargar ferro
pedido.view.cub-download=Descargar cubierta pedido.view.cub-download=Descargar cubierta
pedido.view.tapa-download=Descargar tapa pedido.view.tapa-download=Descargar tapa
pedido.view.admin-actions=Acciones de administrador
pedido.view.cancel-title=¿Estás seguro de que deseas cancelar este pedido?
pedido.view.cancel-text=Esta acción no se puede deshacer.
pedido.errors.linea-not-found=No se ha encontrado la línea de pedido. pedido.errors.linea-not-found=No se ha encontrado la línea de pedido.
pedido.errors.cancel-pedido=Error al cancelar el pedido
pedido.errors.state-error=Estado de línea no válido. pedido.errors.state-error=Estado de línea no válido.
pedido.errors.update-server-error=Error al actualizar el estado desde el servidor externo. pedido.errors.update-server-error=Error al actualizar el estado desde el servidor externo.
pedido.errors.connecting-server-error=Error al conectar con el servidor externo. pedido.errors.connecting-server-error=Error al conectar con el servidor externo.
pedido.errors.cannot-update=No se puede actualizar el estado de una línea con ese estado inicial. pedido.errors.cannot-update=No se puede actualizar el estado de una línea con ese estado inicial.
pedido.success.estado-actualizado=Estado del pedido actualizado correctamente. pedido.success.estado-actualizado=Estado del pedido actualizado correctamente.
pedido.success.same-estado=Sin cambios en el estado. pedido.success.same-estado=Sin cambios en el estado.
pedido.success.pedido-cancelado=Pedido cancelado correctamente.

View File

@ -49,6 +49,7 @@ presupuesto.comentario-administrador=Comentarios
presupuesto.informacion-libro=Información del libro presupuesto.informacion-libro=Información del libro
presupuesto.datos-generales-descripcion=Datos generales del presupuesto presupuesto.datos-generales-descripcion=Datos generales del presupuesto
presupuesto.titulo=Título* presupuesto.titulo=Título*
presupuesto.cliente=Cliente*
presupuesto.autor=Autor presupuesto.autor=Autor
presupuesto.isbn=ISBN presupuesto.isbn=ISBN
presupuesto.tirada=Tirada presupuesto.tirada=Tirada

View File

@ -141,6 +141,79 @@ $(() => {
}); });
} }
}); });
});
$(document).on('click', '.btn-cancel-pedido', function () {
const pedidoId = $(this).data('pedido-id');
if (!pedidoId) {
console.error('No se ha encontrado el ID del pedido.');
return;
}
Swal.fire({
title: window.languageBundle['pedido.view.cancel-title'] || '¿Estás seguro de que deseas cancelar este pedido?',
text: window.languageBundle['pedido.view.cancel-text'] || "Esta acción no se puede deshacer.",
icon: 'warning',
showCancelButton: true,
confirmButtonText: window.languageBundle['app.yes'] || 'Sí, cancelar pedido',
cancelButtonText: window.languageBundle['app.cancel'] || 'No, mantener pedido',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-danger me-2',
cancelButton: 'btn btn-light'
}
}).then((result) => {
if (result.isConfirmed) {
// Llamada AJAX para cancelar el pedido
$.ajax({
url: `/pedidos/cancel/${pedidoId}`,
type: 'POST',
success: function (response) {
if (!response || !response.success) {
Swal.fire({
icon: 'error',
title: response.message || "Error",
timer: 1800,
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary me-2',
cancelButton: 'btn btn-light'
},
showConfirmButton: false
});
}
else {
Swal.fire({
icon: 'success',
title: response.message || "Éxito",
timer: 1800,
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary me-2',
cancelButton: 'btn btn-light'
},
showConfirmButton: false
}).then((result) => {
if (result.dismiss === Swal.DismissReason.timer) {
location.reload();
}
});
}
},
error: function (xhr, status, error) {
console.error('Error al cancelar el pedido:', error);
Swal.fire({
icon: 'error',
title: xhr.responseJSON?.message || 'Error',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
cancelButton: 'btn btn-light' // clases para cancelar
}
});
}
});
}
});
}); });
}) })

View File

@ -332,7 +332,7 @@ export default class PresupuestoWizard {
servicios: this.formData.servicios.servicios, servicios: this.formData.servicios.servicios,
datosMaquetacion: this.formData.servicios.datosMaquetacion, datosMaquetacion: this.formData.servicios.datosMaquetacion,
datosMarcapaginas: this.formData.servicios.datosMarcapaginas, datosMarcapaginas: this.formData.servicios.datosMarcapaginas,
cliente_id: $('#cliente_id').val() || null, cliente_id: $('#user_id').val() || null,
}; };
try { try {
@ -1696,7 +1696,7 @@ export default class PresupuestoWizard {
const body = { const body = {
presupuesto: this.#getPresupuestoData(), presupuesto: this.#getPresupuestoData(),
save: this.opts.canSave, save: this.opts.mode == 'public' ? true : this.opts.canSave,
mode: this.opts.mode, mode: this.opts.mode,
servicios: servicios, servicios: servicios,
datosMaquetacion: this.formData.servicios.datosMaquetacion, datosMaquetacion: this.formData.servicios.datosMaquetacion,

View File

@ -0,0 +1,43 @@
$(() => {
// Inicializar select2 para el campo de cliente en el formulario de presupuesto
if ($('#user_id').length) {
$('#user_id').select2({
allowClear: false,
width: '100%',
ajax: {
url: '/users/api/get-users',
dataType: 'json',
data: function (params) {
return {
q: params.term, // término de búsqueda
page: params.page || 1,
size: 10,
showUsername: true
};
},
delay: 250,
processResults: function (data) {
return {
results: data.results || [],
pagination: data.pagination || { more: false }
};
},
cache: true
},
minimumInputLength: 0
});
// Si hay un valor inicial, cargar y establecer el usuario seleccionado
const initialUserId = $('#user_id').val();
if (initialUserId) {
$.ajax({
url: `/users/api/get-user/${initialUserId}`,
dataType: 'json'
}).then(function (data) {
const option = new Option(`${data.fullName} (${data.userName})`, data.id, true, true);
$('#user_id').append(option).trigger('change');
});
}
}
});

View File

@ -46,7 +46,7 @@
url: '/presupuesto/datatable/clientes', url: '/presupuesto/datatable/clientes',
method: 'GET', method: 'GET',
}, },
order: [[0, 'asc']], order: [[0, 'desc']],
columns: [ columns: [
{ data: 'id', name: 'id', orderable: true }, { data: 'id', name: 'id', orderable: true },
{ data: 'titulo', name: 'titulo', orderable: true }, { data: 'titulo', name: 'titulo', orderable: true },

View File

@ -48,7 +48,7 @@ import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-ut
url: '/presupuesto/datatable/anonimos', url: '/presupuesto/datatable/anonimos',
method: 'GET', method: 'GET',
}, },
order: [[0, 'asc']], order: [[0, 'desc']],
columns: [ columns: [
{ data: 'id', name: 'id', orderable: true }, { data: 'id', name: 'id', orderable: true },
{ data: 'titulo', name: 'titulo', orderable: true }, { data: 'titulo', name: 'titulo', orderable: true },
@ -174,7 +174,7 @@ import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-ut
url: '/presupuesto/datatable/clientes', url: '/presupuesto/datatable/clientes',
method: 'GET', method: 'GET',
}, },
order: [[0, 'asc']], order: [[0, 'desc']],
columns: [ columns: [
{ data: 'id', name: 'id', orderable: true }, { data: 'id', name: 'id', orderable: true },
{ data: 'user', name: 'user.fullName', orderable: true }, { data: 'user', name: 'user.fullName', orderable: true },

View File

@ -134,14 +134,6 @@
<div class="row align-items-center gy-3"> <div class="row align-items-center gy-3">
<div class="col-sm"> <div class="col-sm">
<div class="d-flex flex-wrap my-n1"> <div class="d-flex flex-wrap my-n1">
<!-- Botón cancelar -->
<div th:if="${item.estado.name != 'cancelado' && item.estado.name != 'terminado'}">
<a href="javascript:void(0);" class="d-block text-body p-1 px-2 cancel-item"
th:attr="data-linea-id=${item.lineaId}">
<i class="ri-delete-bin-fill text-muted align-bottom me-1"><span
th:text="#{pedido.cancelar}">Cancelar Pedido</span></i>
</a>
</div>
<!-- Actualizar estado--> <!-- Actualizar estado-->
<div class="update-estado-button" <div class="update-estado-button"
th:if="${item.estado.name != 'cancelado' && item.estado.name != 'maquetacion' && item.estado.name != 'terminado'}"> th:if="${item.estado.name != 'cancelado' && item.estado.name != 'maquetacion' && item.estado.name != 'terminado'}">

View File

@ -38,15 +38,33 @@
<div class="row"> <div class="row">
<div class="col-12 col-md-auto"> <div class="col-12 col-md-auto">
<div th:insert="~{imprimelibros/direcciones/direccionFacturacionCard :: <div th:insert="~{imprimelibros/direcciones/direccionFacturacionCard ::
direccionFacturacionCard( direccionFacturacionCard(
direccion=${direccionFacturacion}, direccion=${direccionFacturacion},
pais=${direccionFacturacion != null ? direccionFacturacion.paisNombre : ''} pais=${direccionFacturacion != null ? direccionFacturacion.paisNombre : ''}
)}"> )}">
</div> </div>
</div> </div>
<th:block th:if="${isAdmin and showCancel}">
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
class="col-12 col-md-auto">
<div class="card card border mb-3 admin-actions">
<div class="card-header bg-light">
<span class="fs-16" th:text="#{'pedido.view.admin-actions'}"></span>
</div>
<div class="card-body">
<button type="button" class="btn btn-danger w-100 btn-cancel-pedido"
th:text="#{pedido.cancelar}" th:attr="data-pedido-id=${id}">
Cancelar pedido
</button>
</div>
</div>
</div>
</th:block>
</div> </div>
<th:block th:each="linea: ${lineas}"> <th:block th:each="linea: ${lineas}">
<div <div
th:insert="~{imprimelibros/pedidos/pedidos-linea :: pedido-linea (item=${linea}, isAdmin=${isAdmin})}"> th:insert="~{imprimelibros/pedidos/pedidos-linea :: pedido-linea (item=${linea}, isAdmin=${isAdmin})}">
@ -76,7 +94,8 @@
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script> <script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/pedidos/pedidos-view.js}"></script> <script type="module" th:src="@{/assets/js/pages/imprimelibros/pedidos/pedidos-view.js}"></script>
<script th:if="${isAdmin}" type="module" th:src="@{/assets/js/pages/imprimelibros/pedidos/pedidos-view-admin.js}"></script> <script th:if="${isAdmin}" type="module"
th:src="@{/assets/js/pages/imprimelibros/pedidos/pedidos-view-admin.js}"></script>
</th:block> </th:block>
</body> </body>

View File

@ -18,6 +18,28 @@
</div> </div>
<div class="px-2"> <div class="px-2">
<th:block th:if="${presupuesto?.user != null}">
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')" class="row">
<div class="col-sm-12">
<div class="mb-3">
<label for="user_id" class="form-label" th:text="#{presupuesto.cliente}">
>Cliente*</label>
<select class="form-select select2 datos-generales-data" id="user_id">
<option
th:value="${presupuesto?.user.id} ?: ''"
th:text="${presupuesto.user != null ? presupuesto.user.fullName + ' (' + presupuesto.user.userName + ')' : ''}"
selected>
</option>
</select>
</div>
</div>
</div>
<div sec:authorize="isAuthenticated() and !hasAnyRole('SUPERADMIN','ADMIN')">
<input type="hidden" class="datos-generales-data" id="user_id"
th:value="${presupuesto?.user.id} ?: ''">
</div>
</th:block>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<div class="mb-3"> <div class="mb-3">

View File

@ -89,6 +89,8 @@
th:src="@{/assets/libs/quill/quill.min.js}"></script> th:src="@{/assets/libs/quill/quill.min.js}"></script>
<script sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')" <script sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
th:src="@{/assets/js/pages/imprimelibros/presupuestador/text-editor.js}"></script> th:src="@{/assets/js/pages/imprimelibros/presupuestador/text-editor.js}"></script>
<script sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')"
th:src="@{/assets/js/pages/imprimelibros/presupuestos/admin-utils.js}"></script>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js}"></script> <script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js}"></script>
</th:block> </th:block>