diff --git a/logs/erp.log b/logs/erp.log index e90c183..69267af 100644 --- a/logs/erp.log +++ b/logs/erp.log @@ -1,23 +1,23 @@ -2025-11-15 08:19:06 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 13129 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:19:06 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:19:10 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 08:19:10 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@ea50607 -2025-11-15 08:19:10 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 08:19:11 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:19:11 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:19:11 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - Previously run: 51 -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - Total change sets: 51 -2025-11-15 08:19:11 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:19:11 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:19:12 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:19:12 INFO [restartedMain] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 08:19:12 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:19:13 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: +2025-12-28 11:38:17 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.9 with PID 10066 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) +2025-12-28 11:38:17 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" +2025-12-28 11:38:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2025-12-28 11:38:22 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6ed6a1c8 +2025-12-28 11:38:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2025-12-28 11:38:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG +2025-12-28 11:38:23 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute +2025-12-28 11:38:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - UPDATE SUMMARY +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - Run: 0 +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - Previously run: 62 +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - Filtered out: 0 +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - ------------------------------- +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - Total change sets: 62 +2025-12-28 11:38:23 INFO [restartedMain] liquibase.util - Update summary generated +2025-12-28 11:38:23 INFO [restartedMain] liquibase.command - Command execution complete +2025-12-28 11:38:24 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] +2025-12-28 11:38:24 INFO [restartedMain] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.39.Final +2025-12-28 11:38:24 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled +2025-12-28 11:38:24 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] Database driver: undefined/unknown Database version: 8.0.43 @@ -25,33 +25,29 @@ Isolation level: undefined/unknown Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown -2025-11-15 08:19:14 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:19:19 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 13.772 seconds (process running for 14.889) -2025-11-15 08:24:35 WARN [http-nio-8080-exec-9] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:24:35 ERROR [http-nio-8080-exec-9] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '000000' for key 'payment_transactions.uq_tx_gateway_txid' -2025-11-15 08:25:30 WARN [http-nio-8080-exec-8] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:25:30 ERROR [http-nio-8080-exec-8] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '000000' for key 'payment_transactions.uq_tx_gateway_txid' -2025-11-15 08:29:20 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 08:29:20 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 08:29:20 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 13129 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:29:20 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:29:21 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Starting... -2025-11-15 08:29:21 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-2 - Added connection com.mysql.cj.jdbc.ConnectionImpl@5582ad4 -2025-11-15 08:29:21 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Start completed. -2025-11-15 08:29:21 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:29:21 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:29:21 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - Previously run: 51 -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - Total change sets: 51 -2025-11-15 08:29:21 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:29:21 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:29:22 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:29:22 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:29:22 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: +2025-12-28 11:38:26 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2025-12-28 11:38:31 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 14.361 seconds (process running for 15.893) +2025-12-28 11:54:36 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2025-12-28 11:54:36 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. +2025-12-28 11:54:36 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.9 with PID 10066 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) +2025-12-28 11:54:36 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" +2025-12-28 11:54:38 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Starting... +2025-12-28 11:54:38 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-2 - Added connection com.mysql.cj.jdbc.ConnectionImpl@4a1a1522 +2025-12-28 11:54:38 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Start completed. +2025-12-28 11:54:38 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG +2025-12-28 11:54:39 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute +2025-12-28 11:54:39 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - UPDATE SUMMARY +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - Run: 0 +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - Previously run: 62 +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - Filtered out: 0 +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - ------------------------------- +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - Total change sets: 62 +2025-12-28 11:54:39 INFO [restartedMain] liquibase.util - Update summary generated +2025-12-28 11:54:39 INFO [restartedMain] liquibase.command - Command execution complete +2025-12-28 11:54:39 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] +2025-12-28 11:54:39 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled +2025-12-28 11:54:39 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-2)'] Database driver: undefined/unknown Database version: 8.0.43 @@ -59,673 +55,5 @@ Isolation level: undefined/unknown Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown -2025-11-15 08:29:22 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:29:23 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 3.113 seconds (process running for 618.77) -2025-11-15 08:29:39 WARN [http-nio-8080-exec-5] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:29:39 ERROR [http-nio-8080-exec-5] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '091619' for key 'payment_transactions.uq_tx_gateway_txid' -2025-11-15 08:29:53 WARN [http-nio-8080-exec-6] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:29:53 ERROR [http-nio-8080-exec-6] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '091606' for key 'payment_transactions.uq_tx_gateway_txid' -2025-11-15 08:35:22 INFO [Thread-7] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown initiated... -2025-11-15 08:35:22 INFO [Thread-7] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown completed. -2025-11-15 08:35:22 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 13129 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:35:22 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:35:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Starting... -2025-11-15 08:35:22 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-3 - Added connection com.mysql.cj.jdbc.ConnectionImpl@28cd0857 -2025-11-15 08:35:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Start completed. -2025-11-15 08:35:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:35:23 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:35:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - Previously run: 51 -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - Total change sets: 51 -2025-11-15 08:35:23 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:35:23 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:35:23 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:35:23 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:35:23 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-3)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 08:35:23 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:35:24 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 2.476 seconds (process running for 979.836) -2025-11-15 08:36:03 INFO [Thread-11] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Shutdown initiated... -2025-11-15 08:36:03 INFO [Thread-11] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Shutdown completed. -2025-11-15 08:36:03 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 13129 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:36:03 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:36:03 ERROR [restartedMain] o.s.boot.SpringApplication - Application run failed -java.lang.NoClassDefFoundError: List - at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na] - at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3578) ~[na:na] - at java.base/java.lang.Class.privateGetPublicMethods(Class.java:3603) ~[na:na] - at java.base/java.lang.Class.getMethods(Class.java:2185) ~[na:na] - at org.springframework.data.util.ReactiveWrappers.usesReactiveType(ReactiveWrappers.java:141) ~[spring-data-commons-3.5.5.jar:3.5.5] - at org.springframework.data.repository.core.support.AbstractRepositoryMetadata.isReactiveRepository(AbstractRepositoryMetadata.java:135) ~[spring-data-commons-3.5.5.jar:3.5.5] - at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.useRepositoryConfiguration(RepositoryConfigurationExtensionSupport.java:344) ~[spring-data-commons-3.5.5.jar:3.5.5] - at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.getRepositoryConfigurations(RepositoryConfigurationExtensionSupport.java:98) ~[spring-data-commons-3.5.5.jar:3.5.5] - at org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn(RepositoryConfigurationDelegate.java:170) ~[spring-data-commons-3.5.5.jar:3.5.5] - at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions(AbstractRepositoryConfigurationSourceSupport.java:62) ~[spring-boot-autoconfigure-3.5.7.jar:3.5.7] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:409) ~[spring-context-6.2.12.jar:6.2.12] - at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:986) ~[na:na] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:408) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:148) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:120) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:430) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:290) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:118) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.12.jar:6.2.12] - at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.7.jar:3.5.7] - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.5.7.jar:3.5.7] - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.7.jar:3.5.7] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.7.jar:3.5.7] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.5.7.jar:3.5.7] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.5.7.jar:3.5.7] - at com.imprimelibros.erp.ErpApplication.main(ErpApplication.java:14) ~[classes/:na] - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na] - at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na] - at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.5.7.jar:3.5.7] -Caused by: java.lang.ClassNotFoundException: List - at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na] - at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na] - at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) ~[na:na] - at java.base/java.lang.Class.forName0(Native Method) ~[na:na] - at java.base/java.lang.Class.forName(Class.java:534) ~[na:na] - at java.base/java.lang.Class.forName(Class.java:513) ~[na:na] - at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) ~[spring-boot-devtools-3.5.7.jar:3.5.7] - at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) ~[na:na] - ... 31 common frames omitted -2025-11-15 08:40:59 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 36905 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:40:59 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:41:03 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 08:41:03 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7d70349b -2025-11-15 08:41:03 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 08:41:04 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:41:04 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:41:04 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - Previously run: 52 -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - Total change sets: 52 -2025-11-15 08:41:04 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:41:04 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:41:05 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:41:05 INFO [restartedMain] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 08:41:05 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:41:05 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 08:41:07 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:41:13 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 13.881 seconds (process running for 15.488) -2025-11-15 08:50:21 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 08:50:21 INFO [Thread-5] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 08:50:21 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 36905 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:50:21 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:50:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Starting... -2025-11-15 08:50:22 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-2 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7a12d5f7 -2025-11-15 08:50:22 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Start completed. -2025-11-15 08:50:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:50:23 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:50:23 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - Previously run: 52 -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - Total change sets: 52 -2025-11-15 08:50:23 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:50:23 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:50:23 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:50:23 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:50:23 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-2)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 08:50:24 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:50:25 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 3.873 seconds (process running for 567.976) -2025-11-15 08:50:39 INFO [Thread-7] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown initiated... -2025-11-15 08:50:39 INFO [Thread-7] com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown completed. -2025-11-15 08:50:40 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 36905 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:50:40 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:50:40 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Starting... -2025-11-15 08:50:40 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-3 - Added connection com.mysql.cj.jdbc.ConnectionImpl@393c3fa3 -2025-11-15 08:50:40 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Start completed. -2025-11-15 08:50:40 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:50:41 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:50:41 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - Previously run: 52 -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - Total change sets: 52 -2025-11-15 08:50:41 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:50:41 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:50:41 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:50:41 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:50:41 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-3)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 08:50:41 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:50:42 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 2.774 seconds (process running for 585.241) -2025-11-15 08:51:28 WARN [http-nio-8080-exec-1] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:51:28 ERROR [http-nio-8080-exec-1] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '341823' for key 'refunds.uq_refund_gateway_id' -2025-11-15 08:51:57 WARN [http-nio-8080-exec-2] o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1062, SQLState: 23000 -2025-11-15 08:51:57 ERROR [http-nio-8080-exec-2] o.h.e.jdbc.spi.SqlExceptionHelper - Duplicate entry '091606' for key 'refunds.uq_refund_gateway_id' -2025-11-15 08:56:05 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Starting ErpApplication using Java 21.0.8 with PID 53621 (/home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros/target/classes started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 08:56:05 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - The following 1 profile is active: "dev" -2025-11-15 08:56:09 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 08:56:09 INFO [restartedMain] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@40832d28 -2025-11-15 08:56:09 INFO [restartedMain] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 08:56:10 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:56:11 INFO [restartedMain] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 08:56:11 INFO [restartedMain] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - UPDATE SUMMARY -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - Run: 0 -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - Previously run: 53 -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - Filtered out: 0 -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - ------------------------------- -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - Total change sets: 53 -2025-11-15 08:56:11 INFO [restartedMain] liquibase.util - Update summary generated -2025-11-15 08:56:11 INFO [restartedMain] liquibase.command - Command execution complete -2025-11-15 08:56:11 INFO [restartedMain] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 08:56:11 INFO [restartedMain] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 08:56:11 INFO [restartedMain] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 08:56:11 INFO [restartedMain] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 08:56:13 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 08:56:19 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 14.185 seconds (process running for 15.876) -2025-11-15 09:22:03 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 81704 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:22:03 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:22:05 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:22:05 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@12214f2f -2025-11-15 09:22:05 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:22:06 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:22:07 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:22:07 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:22:07 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:22:07 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:22:07 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:22:07 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:22:07 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:22:07 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:22:07 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:22:07 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:22:07 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:22:07 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:22:07 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:22:08 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:22:10 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:22:15 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.758 seconds (process running for 14.115) -2025-11-15 09:22:22 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:22:22 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:28:43 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 89069 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:28:43 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:28:46 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:28:46 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7c421952 -2025-11-15 09:28:46 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:28:47 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:28:48 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:28:48 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:28:48 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:28:48 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:28:48 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:28:48 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:28:48 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:28:48 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:28:48 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:28:48 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:28:48 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:28:48 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:28:48 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:28:48 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:28:50 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:28:56 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.922 seconds (process running for 14.229) -2025-11-15 09:29:00 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:29:00 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:36:40 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 97006 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:36:40 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:36:43 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:36:43 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7c421952 -2025-11-15 09:36:43 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:36:44 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:36:44 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:36:44 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:36:44 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:36:44 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:36:44 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:36:44 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:36:44 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:36:44 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:36:44 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:36:44 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:36:44 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:36:44 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:36:44 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:36:45 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:36:47 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:36:52 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.437 seconds (process running for 13.709) -2025-11-15 09:36:55 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:36:55 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:40:43 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 101329 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:40:43 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:40:45 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:40:45 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@17d2b646 -2025-11-15 09:40:45 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:40:46 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:40:47 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:40:47 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:40:47 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:40:47 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:40:47 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:40:47 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:40:47 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:40:47 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:40:47 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:40:47 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:40:47 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:40:47 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:40:47 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:40:48 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:40:50 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:40:55 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.997 seconds (process running for 14.277) -2025-11-15 09:40:58 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:40:58 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:42:43 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 103589 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:42:43 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:42:45 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:42:46 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6cd2cb2 -2025-11-15 09:42:46 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:42:46 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:42:47 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:42:47 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:42:47 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:42:47 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:42:47 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:42:47 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:42:47 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:42:47 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:42:47 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:42:47 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:42:47 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:42:47 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:42:47 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:42:47 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:42:49 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:42:55 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.227 seconds (process running for 13.496) -2025-11-15 09:43:09 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:43:09 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:43:16 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 104336 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:43:16 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:43:18 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:43:19 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6f289728 -2025-11-15 09:43:19 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:43:20 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:43:20 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:43:20 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:43:20 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:43:20 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:43:20 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:43:20 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:43:20 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:43:20 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:43:20 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:43:20 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:43:20 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:43:20 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:43:20 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:43:20 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:43:22 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:43:27 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.406 seconds (process running for 13.785) -2025-11-15 09:44:27 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:44:27 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:44:34 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 105914 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:44:34 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:44:36 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:44:37 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6f289728 -2025-11-15 09:44:37 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:44:38 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:44:38 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:44:38 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:44:38 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:44:38 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:44:38 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:44:38 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:44:38 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:44:38 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:44:38 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:44:38 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:44:38 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:44:38 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:44:38 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:44:39 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:44:40 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:44:45 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.126 seconds (process running for 13.405) -2025-11-15 09:45:44 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:45:44 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 09:45:50 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 107469 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 09:45:50 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 09:45:52 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 09:45:53 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@33215ffb -2025-11-15 09:45:53 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 09:45:54 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:45:54 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 09:45:54 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 09:45:54 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 09:45:54 INFO [main] liquibase.util - Run: 0 -2025-11-15 09:45:54 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 09:45:54 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 09:45:54 INFO [main] liquibase.util - ------------------------------- -2025-11-15 09:45:54 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 09:45:54 INFO [main] liquibase.util - Update summary generated -2025-11-15 09:45:54 INFO [main] liquibase.command - Command execution complete -2025-11-15 09:45:54 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 09:45:54 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 09:45:54 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 09:45:54 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 09:45:56 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 09:46:01 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.001 seconds (process running for 13.345) -2025-11-15 09:46:17 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 09:46:17 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:04:46 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 126526 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:04:46 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:04:49 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:04:49 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6cd2cb2 -2025-11-15 10:04:49 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:04:50 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:04:50 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:04:50 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:04:50 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:04:50 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:04:50 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:04:50 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:04:50 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:04:50 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:04:50 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:04:50 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:04:50 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:04:50 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:04:50 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:04:51 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:04:53 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:04:58 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.902 seconds (process running for 14.284) -2025-11-15 10:05:48 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:05:48 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:09:03 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 131295 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:09:03 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:09:06 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:09:06 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7de6549d -2025-11-15 10:09:06 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:09:07 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:09:07 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:09:07 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:09:07 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:09:07 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:09:07 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:09:07 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:09:07 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:09:07 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:09:07 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:09:07 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:09:07 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:09:07 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:09:08 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:09:08 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:09:10 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:09:15 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.299 seconds (process running for 13.566) -2025-11-15 10:10:21 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:10:21 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:10:53 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 133492 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:10:53 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:10:55 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:10:55 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@33215ffb -2025-11-15 10:10:55 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:10:56 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:10:57 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:10:57 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:10:57 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:10:57 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:10:57 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:10:57 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:10:57 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:10:57 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:10:57 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:10:57 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:10:57 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:10:57 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:10:57 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:10:57 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:10:59 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:11:05 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.798 seconds (process running for 14.082) -2025-11-15 10:11:36 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:11:36 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:12:13 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 135015 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:12:13 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:12:16 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:12:16 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7ffcb232 -2025-11-15 10:12:16 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:12:18 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:12:18 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:12:18 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:12:18 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:12:18 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:12:18 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:12:18 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:12:18 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:12:18 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:12:18 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:12:18 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:12:18 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:12:18 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:12:18 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:12:19 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:12:21 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:12:26 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 13.246 seconds (process running for 14.87) -2025-11-15 10:22:31 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:22:31 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:22:40 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 146328 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:22:40 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:22:43 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:22:43 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6f289728 -2025-11-15 10:22:43 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:22:44 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:22:44 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:22:44 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:22:44 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:22:44 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:22:44 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:22:44 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:22:44 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:22:44 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:22:44 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:22:44 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:22:44 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:22:44 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:22:44 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:22:45 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:22:47 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:22:52 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.681 seconds (process running for 13.963) -2025-11-15 10:24:16 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:24:16 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:24:21 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 148308 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:24:21 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:24:24 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:24:24 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6cd2cb2 -2025-11-15 10:24:24 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:24:25 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:24:25 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:24:25 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:24:25 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:24:25 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:24:25 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:24:25 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:24:25 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:24:25 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:24:25 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:24:25 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:24:25 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:24:25 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:24:25 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:24:26 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:24:28 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:24:33 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.138 seconds (process running for 13.409) -2025-11-15 10:24:42 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:24:42 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. -2025-11-15 10:29:55 INFO [main] c.i.erp.cart.envioCarroTest - Starting envioCarroTest using Java 21.0.8 with PID 154243 (started by jjimenez in /home/jjimenez/DEVELOPMENT/01_PROGRAMMING/erp-imprimelibros) -2025-11-15 10:29:55 INFO [main] c.i.erp.cart.envioCarroTest - The following 1 profile is active: "dev" -2025-11-15 10:29:58 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... -2025-11-15 10:29:58 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@33215ffb -2025-11-15 10:29:58 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. -2025-11-15 10:29:59 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:29:59 INFO [main] liquibase.ui - Database is up to date, no changesets to execute -2025-11-15 10:29:59 INFO [main] liquibase.changelog - Reading from imprimelibros.DATABASECHANGELOG -2025-11-15 10:29:59 INFO [main] liquibase.util - UPDATE SUMMARY -2025-11-15 10:29:59 INFO [main] liquibase.util - Run: 0 -2025-11-15 10:29:59 INFO [main] liquibase.util - Previously run: 53 -2025-11-15 10:29:59 INFO [main] liquibase.util - Filtered out: 0 -2025-11-15 10:29:59 INFO [main] liquibase.util - ------------------------------- -2025-11-15 10:29:59 INFO [main] liquibase.util - Total change sets: 53 -2025-11-15 10:29:59 INFO [main] liquibase.util - Update summary generated -2025-11-15 10:29:59 INFO [main] liquibase.command - Command execution complete -2025-11-15 10:29:59 INFO [main] o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] -2025-11-15 10:29:59 INFO [main] org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.33.Final -2025-11-15 10:29:59 INFO [main] o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled -2025-11-15 10:30:00 INFO [main] o.hibernate.orm.connections.pooling - HHH10001005: Database info: - Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] - Database driver: undefined/unknown - Database version: 8.0.43 - Autocommit mode: undefined/unknown - Isolation level: undefined/unknown - Minimum pool size: undefined/unknown - Maximum pool size: undefined/unknown -2025-11-15 10:30:02 INFO [main] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2025-11-15 10:30:07 INFO [main] c.i.erp.cart.envioCarroTest - Started envioCarroTest in 12.337 seconds (process running for 13.593) -2025-11-15 10:30:11 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... -2025-11-15 10:30:11 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. +2025-12-28 11:54:40 INFO [restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2025-12-28 11:54:41 INFO [restartedMain] c.imprimelibros.erp.ErpApplication - Started ErpApplication in 5.124 seconds (process running for 986.129) diff --git a/pom.xml b/pom.xml index f289e38..a754caf 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 3.5.9 com.imprimelibros diff --git a/src/main/java/com/imprimelibros/erp/cart/CartService.java b/src/main/java/com/imprimelibros/erp/cart/CartService.java index 5b00af0..48b1a18 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartService.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartService.java @@ -5,6 +5,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.context.MessageSource; import org.springframework.stereotype.Service; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,41 +21,41 @@ import com.imprimelibros.erp.cart.dto.DireccionCardDTO; import com.imprimelibros.erp.cart.dto.DireccionShipment; import com.imprimelibros.erp.cart.dto.UpdateCartRequest; import com.imprimelibros.erp.common.Utils; +import com.imprimelibros.erp.common.email.EmailService; import com.imprimelibros.erp.direcciones.DireccionService; import com.imprimelibros.erp.externalApi.skApiClient; -import com.imprimelibros.erp.pedidos.Pedido; -import com.imprimelibros.erp.pedidos.PedidoService; +import com.imprimelibros.erp.pedidos.PedidoRepository; import com.imprimelibros.erp.presupuesto.PresupuestoRepository; @Service public class CartService { + private final EmailService emailService; + private final CartRepository cartRepo; private final CartDireccionRepository cartDireccionRepo; private final CartItemRepository itemRepo; private final MessageSource messageSource; private final PresupuestoRepository presupuestoRepo; - private final Utils utils; private final DireccionService direccionService; private final skApiClient skApiClient; - private final PedidoService pedidoService; private final PresupuestoService presupuestoService; + private final PedidoRepository pedidoRepository; public CartService(CartRepository cartRepo, CartItemRepository itemRepo, CartDireccionRepository cartDireccionRepo, MessageSource messageSource, - PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, - Utils utils, DireccionService direccionService, skApiClient skApiClient, - PedidoService pedidoService, PresupuestoService presupuestoService) { + PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, PedidoRepository pedidoRepository, + DireccionService direccionService, skApiClient skApiClient,PresupuestoService presupuestoService, EmailService emailService) { this.cartRepo = cartRepo; this.itemRepo = itemRepo; this.cartDireccionRepo = cartDireccionRepo; this.messageSource = messageSource; this.presupuestoRepo = presupuestoRepo; - this.utils = utils; this.direccionService = direccionService; this.skApiClient = skApiClient; - this.pedidoService = pedidoService; this.presupuestoService = presupuestoService; + this.emailService = emailService; + this.pedidoRepository = pedidoRepository; } public Cart findById(Long cartId) { @@ -89,7 +90,7 @@ public class CartService { Presupuesto p = item.getPresupuesto(); - Map elemento = getElementoCart(p, locale); + Map elemento = presupuestoService.getPresupuestoInfoForCard(p, locale); elemento.put("cartItemId", item.getId()); resultados.add(elemento); } @@ -159,38 +160,6 @@ public class CartService { return itemRepo.findByCartId(cart.getId()).size(); } - private Map getElementoCart(Presupuesto presupuesto, Locale locale) { - - Map resumen = new HashMap<>(); - - resumen.put("titulo", presupuesto.getTitulo()); - - resumen.put("imagen", - "/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion() + ".png"); - resumen.put("imagen_alt", - messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null, locale)); - - resumen.put("presupuestoId", presupuesto.getId()); - - if (presupuesto.getServiciosJson() != null && presupuesto.getServiciosJson().contains("ejemplar-prueba")) { - resumen.put("hasSample", true); - } else { - resumen.put("hasSample", false); - } - Map detalles = utils.getTextoPresupuesto(presupuesto, locale); - - resumen.put("tirada", presupuesto.getSelectedTirada()); - - resumen.put("baseTotal", Utils.formatCurrency(presupuesto.getBaseImponible(), locale)); - resumen.put("base", presupuesto.getBaseImponible()); - resumen.put("iva4", presupuesto.getIvaImporte4()); - resumen.put("iva21", presupuesto.getIvaImporte21()); - - resumen.put("resumen", detalles); - - return resumen; - } - public Map getCartSummaryRaw(Cart cart, Locale locale) { double base = 0.0; @@ -298,7 +267,7 @@ public class CartService { } double totalBeforeDiscount = base + iva4 + iva21 + shipment; - int fidelizacion = pedidoService.getDescuentoFidelizacion(); + int fidelizacion = this.getDescuentoFidelizacion(cart.getUserId()); double descuento = totalBeforeDiscount * fidelizacion / 100.0; double total = totalBeforeDiscount - descuento; @@ -325,6 +294,27 @@ public class CartService { return summary; } + public int getDescuentoFidelizacion(Long userId) { + // descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el + // ultimo año) + Instant haceUnAno = Instant.now().minusSeconds(365 * 24 * 60 * 60); + double totalGastado = pedidoRepository.sumTotalByCreatedByAndCreatedAtAfter(userId, haceUnAno); + if (totalGastado < 1200) { + return 0; + } else if (totalGastado >= 1200 && totalGastado < 1999) { + return 1; + } else if (totalGastado >= 2000 && totalGastado < 2999) { + return 2; + } else if (totalGastado >= 3000 && totalGastado < 3999) { + return 3; + } else if (totalGastado >= 4000 && totalGastado < 4999) { + return 4; + } else if (totalGastado >= 5000) { + return 5; + } + return 0; + } + public Map getCartSummary(Cart cart, Locale locale) { Map raw = getCartSummaryRaw(cart, locale); @@ -445,175 +435,6 @@ public class CartService { cartDireccionRepo.deleteByDireccionIdAndCartStatus(direccionId, Cart.Status.ACTIVE); } - @Transactional - public Long crearPedido(Long cartId, Locale locale) { - - Cart cart = this.getCartById(cartId); - List items = cart.getItems(); - - List> presupuestoRequests = new ArrayList<>(); - List presupuestoIds = new ArrayList<>(); - - for (Integer i = 0; i < items.size(); i++) { - CartItem item = items.get(i); - Presupuesto pCart = item.getPresupuesto(); - - // Asegurarnos de trabajar con la entidad gestionada por JPA - Presupuesto p = presupuestoRepo.findById(pCart.getId()) - .orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId())); - - Map data_to_send = presupuestoService.toSkApiRequest(p, true); - data_to_send.put("createPedido", 0); - - // Recuperar el mapa anidado datosCabecera - @SuppressWarnings("unchecked") - Map datosCabecera = (Map) data_to_send.get("datosCabecera"); - if (datosCabecera != null) { - Object tituloOriginal = datosCabecera.get("titulo"); - datosCabecera.put( - "titulo", - "[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : "")); - } - - Map direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p); - data_to_send.put("direcciones", direcciones_presupuesto.get("direcciones")); - data_to_send.put("direccionesFP1", direcciones_presupuesto.get("direccionesFP1")); - - Map result = skApiClient.savePresupuesto(data_to_send); - - if (result.containsKey("error")) { - System.out.println("Error al guardar presupuesto en SK"); - System.out.println("-------------------------"); - System.out.println(result.get("error")); - // decide si seguir con otros items o abortar: - // continue; o bien throw ... - continue; - } - - Object dataObj = result.get("data"); - if (!(dataObj instanceof Map dataRaw)) { - System.out.println("Formato inesperado de 'data' en savePresupuesto: " + result); - continue; - } - - @SuppressWarnings("unchecked") - Map dataMap = (Map) dataRaw; - Long presId = ((Number) dataMap.get("id")).longValue(); - String skin = ((String) dataMap.get("iskn")).toString(); - p.setProveedor("Safekat"); - p.setProveedorRef1(skin); - p.setProveedorRef2(presId); - p.setEstado(Presupuesto.Estado.aceptado); - presupuestoRepo.save(p); - - presupuestoIds.add(p.getId()); - - presupuestoRequests.add(dataMap); - } - - // Crear el pedido en base a los presupuestos guardados - if (presupuestoRequests.isEmpty()) { - throw new IllegalStateException("No se pudieron guardar los presupuestos en SK."); - } else { - ArrayList presupuestoSkIds = new ArrayList<>(); - for (Map presData : presupuestoRequests) { - Long presId = ((Number) presData.get("id")).longValue(); - presupuestoSkIds.add(presId); - } - Map ids = new HashMap<>(); - ids.put("presupuesto_ids", presupuestoSkIds); - Long pedidoId = skApiClient.crearPedido(ids); - if (pedidoId == null) { - throw new IllegalStateException("No se pudo crear el pedido en SK."); - } - Pedido pedidoInterno = pedidoService.crearPedido(presupuestoIds, this.getCartSummaryRaw(cart, locale), - "Safekat", String.valueOf(pedidoId), cart.getUserId()); - return pedidoInterno.getId(); - } - } - - public Map getDireccionesPresupuesto(Cart cart, Presupuesto presupuesto) { - - List> direccionesPresupuesto = new ArrayList<>(); - List> direccionesPrueba = new ArrayList<>(); - if (cart.getOnlyOneShipment()) { - List direcciones = cart.getDirecciones().stream().limit(1).toList(); - if (!direcciones.isEmpty()) { - if (presupuesto.getServiciosJson() != null - && presupuesto.getServiciosJson().contains("deposito-legal")) { - direccionesPresupuesto.add(direcciones.get(0).toSkMap( - presupuesto.getSelectedTirada()-4, - presupuesto.getPeso(), - direcciones.get(0).getIsPalets(), - false)); - - direccionesPresupuesto.add(direcciones.get(0).toSkMapDepositoLegal()); - } - else { - direccionesPresupuesto.add(direcciones.get(0).toSkMap( - presupuesto.getSelectedTirada(), - presupuesto.getPeso(), - direcciones.get(0).getIsPalets(), - false)); - } - if (presupuesto.getServiciosJson() != null - && presupuesto.getServiciosJson().contains("ejemplar-prueba")) { - direccionesPrueba.add(direcciones.get(0).toSkMap( - 1, - presupuesto.getPeso(), - false, - true)); - } - - Map direccionesRet = new HashMap<>(); - direccionesRet.put("direcciones", direccionesPresupuesto); - if (!direccionesPrueba.isEmpty()) - direccionesRet.put("direccionesFP1", direccionesPrueba.get(0)); - else { - direccionesRet.put("direccionesFP1", new ArrayList<>()); - } - return direccionesRet; - } - } else { - List direcciones = cart.getDirecciones().stream() - .filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(presupuesto.getId())) - .toList(); - - for (CartDireccion cd : direcciones) { - - // direccion de ejemplar de prueba - if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) { - continue; - } - if (cd.getUnidades() == null || cd.getUnidades() <= 0) { - direccionesPrueba.add(cd.toSkMap( - 1, - presupuesto.getPeso(), - false, - true)); - } else { - direccionesPresupuesto.add(cd.toSkMap( - cd.getUnidades(), - presupuesto.getPeso(), - cd.getIsPalets(), - false)); - } - } - if (presupuesto.getServiciosJson() != null - && presupuesto.getServiciosJson().contains("deposito-legal")) { - CartDireccion cd = new CartDireccion(); - direccionesPresupuesto.add(cd.toSkMapDepositoLegal()); - } - } - Map direccionesRet = new HashMap<>(); - direccionesRet.put("direcciones", direccionesPresupuesto); - if (!direccionesPrueba.isEmpty()) - direccionesRet.put("direccionesFP1", direccionesPrueba.get(0)); - else { - direccionesRet.put("direccionesFP1", new ArrayList<>()); - } - return direccionesRet; - } /*************************************** * MÉTODOS PRIVADOS diff --git a/src/main/java/com/imprimelibros/erp/common/Utils.java b/src/main/java/com/imprimelibros/erp/common/Utils.java index c7ce53d..2f96921 100644 --- a/src/main/java/com/imprimelibros/erp/common/Utils.java +++ b/src/main/java/com/imprimelibros/erp/common/Utils.java @@ -4,7 +4,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.security.Principal; import java.text.NumberFormat; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; @@ -12,6 +14,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.BiFunction; import org.springframework.context.MessageSource; @@ -357,4 +360,62 @@ public class Utils { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm", locale); return dateTime.format(formatter); } + + public static String formatDate(LocalDateTime dateTime, Locale locale) { + if (dateTime == null) { + return ""; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy", locale); + return dateTime.format(formatter); + } + + public static String formatInstant(Instant instant, Locale locale) { + if (instant == null) { + return ""; + } + ZoneId zone = zoneIdForLocale(locale); + + DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("dd/MM/yyyy HH:mm", locale) + .withZone(zone); + + return formatter.format(instant); + } + + /********************* + * Metodos auxiliares + */ + private static ZoneId zoneIdForLocale(Locale locale) { + if (locale == null || locale.getCountry().isEmpty()) { + return ZoneId.of("UTC"); + } + + // Buscar timezones cuyo ID termine con el country code + // Ej: ES -> Europe/Madrid + String country = locale.getCountry(); + + Set zoneIds = ZoneId.getAvailableZoneIds(); + for (String id : zoneIds) { + // TimeZone# getID() no funciona por país, pero sí el prefijo + país + if (id.endsWith("/" + country) || id.contains("/" + country)) { + return ZoneId.of(id); + } + } + + // fallback por regiones comunes (manual pero muy útil) + Map fallback = Map.of( + "ES", "Europe/Madrid", + "MX", "America/Mexico_City", + "AR", "America/Argentina/Buenos_Aires", + "US", "America/New_York", + "GB", "Europe/London", + "FR", "Europe/Paris"); + + if (fallback.containsKey(country)) { + return ZoneId.of(fallback.get(country)); + } + + return ZoneId.systemDefault(); // último fallback + } + } diff --git a/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java b/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java index 27c6a59..220d63a 100644 --- a/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java +++ b/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java @@ -229,6 +229,7 @@ public class skApiClient { Long id = ((Integer) responseBody.get("id")).longValue(); if (success != null && id != null && success) { + return Map.of("data", id); } else { // Tu lógica actual: si success es true u otra cosa → error 2 @@ -247,7 +248,7 @@ public class skApiClient { return (Long) result.get("data"); } - public Integer getMaxSolapas(Map requestBody, Locale locale) { + public Map getMaxSolapas(Map requestBody, Locale locale) { try { String jsonResponse = performWithRetry(() -> { String url = this.skApiUrl + "api/calcular-solapas"; @@ -288,7 +289,11 @@ public class skApiClient { messageSource.getMessage("presupuesto.errores.error-interior", new Object[] { 1 }, locale)); } - return root.get("data").asInt(); + Integer maxSolapas = root.get("data").asInt(); + Double lomo = root.get("lomo").asDouble(); + return Map.of( + "maxSolapas", maxSolapas, + "lomo", lomo); } catch (JsonProcessingException e) { // Fallback al 80% del ancho @@ -301,7 +306,9 @@ public class skApiClient { throw new RuntimeException("Tamaño no válido en la solicitud: " + requestBody); else { int ancho = (int) tamanio.get("ancho"); - return (int) Math.floor(ancho * 0.8); // 80% del ancho + return Map.of( + "maxSolapas", (int) (ancho * 0.8), + "lomo", 0.0); } } } @@ -388,6 +395,187 @@ public class skApiClient { } + public Map checkPedidoEstado(Long presupuestoId, Locale locale) { + + try { + + String jsonResponse = performWithRetry(() -> { + String url = this.skApiUrl + "api/estado-pedido/" + presupuestoId; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authService.getToken()); // token actualizado + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + String.class); + + return response.getBody(); + }); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(jsonResponse); + + if (root.get("data") == null) { + throw new RuntimeException( + "Sin respuesta desde el servidor del proveedor"); + } + + String estado = root.get("data").asText(); + return Map.of( + "estado", estado); + + } catch (JsonProcessingException e) { + // Fallback al 80% del ancho + return Map.of( + "estado", null); + } + } + + public Map getFilesTypes(Long presupuestoId, Locale locale) { + + try { + + Map result = performWithRetryMap(() -> { + String url = this.skApiUrl + "api/files-presupuesto/" + presupuestoId; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authService.getToken()); // token actualizado + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + String.class); + + ObjectMapper mapper = new ObjectMapper(); + + try { + Map responseBody = mapper.readValue( + response.getBody(), + new TypeReference>() { + }); + + // Si la API devuelve "error" a nivel raíz + if (responseBody.get("error") != null) { + // Devolvemos un mapa con sólo el error para que el caller decida + return Map.of("error", responseBody.get("error")); + } + + Boolean hasError = (Boolean) (responseBody.get("error") == null + || responseBody.get("error") == "null" ? false : true); + Map files = (Map) responseBody.get("data"); + + if (files != null && !hasError) { + return Map.of("data", files); + } else { + // Tu lógica actual: si success es true u otra cosa → error 2 + return Map.of("error", 2); + } + + } catch (JsonProcessingException e) { + e.printStackTrace(); + return Map.of("error", 1); + } + }); + + if (result.get("error") != null) { + throw new RuntimeException( + messageSource.getMessage("pedido.errors.connecting-server-error", null, locale)); + } + Map data = (Map) result.get("data"); + return data; + + } catch (RuntimeException e) { + throw new RuntimeException( + messageSource.getMessage("pedido.errors.connecting-server-error", null, locale)); + + } + + } + + public byte[] downloadFile(Long presupuestoId, String fileType, Locale locale) { + return performWithRetryBytes(() -> { + + String normalized = (fileType == null) ? "" : fileType.trim().toLowerCase(); + + String endpoint = switch (normalized) { + case "ferro" -> "api/get-ferro/" + presupuestoId; + case "cubierta" -> "api/get-cubierta/" + presupuestoId; + case "tapa" -> "api/get-tapa/" + presupuestoId; + default -> throw new IllegalArgumentException("Tipo de fichero no soportado: " + fileType); + }; + + // OJO: skApiUrl debería terminar en "/" para que concatene bien + String url = this.skApiUrl + endpoint; + + HttpHeaders headers = new HttpHeaders(); + // Si tu CI4 requiere Bearer, mantenlo. Si NO lo requiere, puedes quitar esta + // línea. + headers.setBearerAuth(authService.getToken()); + headers.setAccept(List.of(MediaType.APPLICATION_PDF, MediaType.APPLICATION_OCTET_STREAM)); + + try { + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + new HttpEntity<>(headers), + byte[].class); + + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody(); // bytes del PDF + } + return null; + + } catch (HttpClientErrorException.NotFound e) { + // CI4 no tiene ese fichero + return null; + } + }); + } + + public Boolean aceptarFerro(Long presupuestoId, Locale locale) { + + String result = performWithRetry(() -> { + String url = this.skApiUrl + "api/aceptar-ferro/" + presupuestoId; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authService.getToken()); + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + entity, + String.class); + + try { + Map responseBody = new ObjectMapper().readValue( + response.getBody(), + new TypeReference>() { + }); + + 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 ******************/ @@ -419,6 +607,19 @@ public class skApiClient { } } + private byte[] performWithRetryBytes(Supplier request) { + try { + return request.get(); + } catch (HttpClientErrorException.Unauthorized e) { + authService.invalidateToken(); + try { + return request.get(); + } catch (HttpClientErrorException ex) { + throw new RuntimeException("La autenticación ha fallado tras renovar el token.", ex); + } + } + } + private static BigDecimal calcularMargen( BigDecimal importe, BigDecimal importeMin, BigDecimal importeMax, BigDecimal margenMax, BigDecimal margenMin) { diff --git a/src/main/java/com/imprimelibros/erp/paises/PaisesService.java b/src/main/java/com/imprimelibros/erp/paises/PaisesService.java index d7b2769..b1909fb 100644 --- a/src/main/java/com/imprimelibros/erp/paises/PaisesService.java +++ b/src/main/java/com/imprimelibros/erp/paises/PaisesService.java @@ -69,4 +69,18 @@ public class PaisesService { } } + public String getPaisNombrePorCode3(String code3, Locale locale) { + if (code3 == null || code3.isEmpty()) { + return ""; + } + Optional opt = repo.findByCode3(code3); + if (opt.isPresent()) { + Paises pais = opt.get(); + String key = pais.getKeyword(); + return messageSource.getMessage("paises." + key, null, key, locale); + } else { + return ""; + } + } + } diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java index d412934..de15e83 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java @@ -16,6 +16,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.datatables.DataTable; import com.imprimelibros.erp.datatables.DataTablesParser; @@ -98,7 +100,7 @@ public class PaymentController { Specification base = Specification.allOf( (root, query, cb) -> cb.equal(root.get("status"), PaymentTransactionStatus.succeeded)); base = base.and((root, query, cb) -> cb.equal(root.get("type"), PaymentTransactionType.CAPTURE)); - + base = base.and((root, query, cb) -> cb.notEqual(root.join("payment").get("gateway"), "bank_transfer")); String clientSearch = dt.getColumnSearch("client"); // 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification @@ -229,10 +231,12 @@ public class PaymentController { }) .add("transfer_id", pago -> { if (pago.getPayment() != null) { - return "TRANSF-" + pago.getPayment().getOrderId(); - } else { - return ""; + Long pedido = pago.getPayment().getOrderId(); + if (pedido != null) { + return "TRANSF-" + pedido; + } } + return ""; }) .add("order_id", pago -> { if (pago.getStatus() != PaymentTransactionStatus.pending) { diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java index 6422397..8298529 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java @@ -13,9 +13,13 @@ import com.imprimelibros.erp.redsys.RedsysService.RedsysNotification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.imprimelibros.erp.payments.repo.WebhookEventRepository; +import com.imprimelibros.erp.pedidos.Pedido; +import com.imprimelibros.erp.pedidos.PedidoLinea; +import com.imprimelibros.erp.pedidos.PedidoService; import java.time.LocalDateTime; import java.util.Locale; +import java.util.Map; import java.util.Objects; @Service @@ -28,18 +32,61 @@ public class PaymentService { private final WebhookEventRepository webhookEventRepo; private final ObjectMapper om = new ObjectMapper(); private final CartService cartService; + private final PedidoService pedidoService; public PaymentService(PaymentRepository payRepo, PaymentTransactionRepository txRepo, RefundRepository refundRepo, RedsysService redsysService, - WebhookEventRepository webhookEventRepo, CartService cartService) { + WebhookEventRepository webhookEventRepo, + CartService cartService, + PedidoService pedidoService) { this.payRepo = payRepo; this.txRepo = txRepo; this.refundRepo = refundRepo; this.redsysService = redsysService; this.webhookEventRepo = webhookEventRepo; this.cartService = cartService; + this.pedidoService = pedidoService; + } + + public Payment findFailedPaymentByOrderId(Long orderId) { + return payRepo.findFirstByOrderIdAndStatusOrderByIdDesc(orderId, PaymentStatus.failed) + .orElse(null); + } + + public Map getPaymentTransactionData(Long paymentId) { + PaymentTransaction tx = txRepo.findByPaymentIdAndType( + paymentId, + PaymentTransactionType.CAPTURE) + .orElse(null); + if (tx == null) { + return null; + } + String resp_payload = tx.getResponsePayload(); + try { + ObjectMapper om = new ObjectMapper(); + var node = om.readTree(resp_payload); + Long cartId = null; + Long dirFactId = null; + if (node.has("Ds_MerchantData")) { + // format: "Ds_MerchantData": "{"dirFactId":3,"cartId":90}" + String merchantData = node.get("Ds_MerchantData").asText(); + merchantData = merchantData.replace(""", "\""); + var mdNode = om.readTree(merchantData); + if (mdNode.has("cartId")) { + cartId = mdNode.get("cartId").asLong(); + } + if (mdNode.has("dirFactId")) { + dirFactId = mdNode.get("dirFactId").asLong(); + } + } + return Map.of( + "cartId", cartId, + "dirFactId", dirFactId); + } catch (Exception e) { + return null; + } } /** @@ -47,14 +94,15 @@ public class PaymentService { * oficial (ApiMacSha256). */ @Transactional - public FormPayload createRedsysPayment(Long cartId, long amountCents, String currency, String method) + public FormPayload createRedsysPayment(Long cartId, Long dirFactId, Long amountCents, String currency, String method, Long orderId) throws Exception { Payment p = new Payment(); - p.setOrderId(null); + p.setOrderId(orderId); Cart cart = this.cartService.findById(cartId); if (cart != null && cart.getUserId() != null) { p.setUserId(cart.getUserId()); + this.cartService.lockCartById(cartId); } p.setCurrency(currency); p.setAmountTotalCents(amountCents); @@ -62,10 +110,6 @@ public class PaymentService { p.setStatus(PaymentStatus.requires_payment_method); p = payRepo.saveAndFlush(p); - // ANTES: - // String dsOrder = String.format("%012d", p.getId()); - - // AHORA: timestamp long now = System.currentTimeMillis(); String dsOrder = String.format("%012d", now % 1_000_000_000_000L); @@ -73,7 +117,7 @@ public class PaymentService { payRepo.save(p); RedsysService.PaymentRequest req = new RedsysService.PaymentRequest(dsOrder, amountCents, - "Compra en Imprimelibros", cartId); + "Compra en Imprimelibros", cartId, dirFactId); if ("bizum".equalsIgnoreCase(method)) { return redsysService.buildRedirectFormBizum(req); @@ -207,13 +251,12 @@ public class PaymentService { p.setAmountCapturedCents(p.getAmountCapturedCents() + notif.amountCents); p.setAuthorizedAt(LocalDateTime.now()); p.setCapturedAt(LocalDateTime.now()); + pedidoService.setOrderAsPaid(p.getOrderId()); + } else { p.setStatus(PaymentStatus.failed); p.setFailedAt(LocalDateTime.now()); - } - - if (authorized) { - processOrder(notif.cartId, locale); + pedidoService.markPedidoAsPaymentDenied(p.getOrderId()); } payRepo.save(p); @@ -308,15 +351,13 @@ public class PaymentService { } @Transactional - public Payment createBankTransferPayment(Long cartId, long amountCents, String currency) { + public Payment createBankTransferPayment(Long cartId, Long dirFactId, long amountCents, String currency, Locale locale, Long orderId) { Payment p = new Payment(); p.setOrderId(null); Cart cart = this.cartService.findById(cartId); if (cart != null && cart.getUserId() != null) { p.setUserId(cart.getUserId()); - // En el orderId de la transferencia pendiente guardamos el ID del carrito - p.setOrderId(cartId); // Se bloquea el carrito para evitar modificaciones mientras se procesa el pago this.cartService.lockCartById(cartId); } @@ -325,6 +366,9 @@ public class PaymentService { p.setAmountTotalCents(amountCents); p.setGateway("bank_transfer"); p.setStatus(PaymentStatus.requires_action); // pendiente de ingreso + if (orderId != null) { + p.setOrderId(orderId); + } p = payRepo.save(p); // Crear transacción pendiente @@ -334,6 +378,18 @@ public class PaymentService { tx.setStatus(PaymentTransactionStatus.pending); tx.setAmountCents(amountCents); tx.setCurrency(currency); + String payload = ""; + if (cartId != null) { + payload = "{\"cartId\":" + cartId + "}"; + } + if (dirFactId != null) { + if (!payload.isEmpty()) { + payload = payload.substring(0, payload.length() - 1) + ",\"dirFactId\":" + dirFactId + "}"; + } else { + payload = "{\"dirFactId\":" + dirFactId + "}"; + } + } + tx.setResponsePayload(payload); // tx.setProcessedAt(null); // la dejas nula hasta que se confirme txRepo.save(tx); @@ -374,12 +430,37 @@ public class PaymentService { p.setAmountCapturedCents(p.getAmountTotalCents()); p.setCapturedAt(LocalDateTime.now()); p.setStatus(PaymentStatus.captured); - payRepo.save(p); - // 4) Procesar el pedido asociado al carrito (si existe) - if (p.getOrderId() != null) { - processOrder(p.getOrderId(), locale); + Long cartId = null; + Long dirFactId = null; + try { + // Intentar extraer cartId del payload de la transacción + if (tx.getResponsePayload() != null && !tx.getResponsePayload().isBlank()) { + ObjectMapper om = new ObjectMapper(); + var node = om.readTree(tx.getResponsePayload()); + if (node.has("cartId")) { + cartId = node.get("cartId").asLong(); + } + if (node.has("dirFactId")) { + dirFactId = node.get("dirFactId").asLong(); + } + } + } catch (Exception e) { + // ignorar } + + // 4) Procesar el pedido asociado al carrito (si existe) o marcar el pedido como pagado + if(p.getOrderId() != null) { + pedidoService.setOrderAsPaid(p.getOrderId()); + } + /*else if (cartId != null) { + // Se procesa el pedido dejando el estado calculado en processOrder + Long orderId = processOrder(cartId, dirFactId, locale, null); + if (orderId != null) { + p.setOrderId(orderId); + } + }*/ + payRepo.save(p); } /** @@ -474,29 +555,5 @@ public class PaymentService { return code >= 0 && code <= 99; } - /** - * Procesa el pedido asociado al carrito: - * - bloquea el carrito - * - crea el pedido a partir del carrito - * - */ - @Transactional - private Boolean processOrder(Long cartId, Locale locale) { - - Cart cart = this.cartService.findById(cartId); - if (cart != null) { - // Bloqueamos el carrito - this.cartService.lockCartById(cart.getId()); - // Creamos el pedido - Long orderId = this.cartService.crearPedido(cart.getId(), locale); - if (orderId == null) { - return false; - } else { - // envio de correo de confirmacion de pedido podria ir aqui - } - - } - return true; - } } diff --git a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentRepository.java b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentRepository.java index 6af17f7..c965437 100644 --- a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentRepository.java +++ b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentRepository.java @@ -2,10 +2,13 @@ package com.imprimelibros.erp.payments.repo; import com.imprimelibros.erp.payments.model.Payment; +import com.imprimelibros.erp.payments.model.PaymentStatus; + import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface PaymentRepository extends JpaRepository { Optional findByGatewayAndGatewayOrderId(String gateway, String gatewayOrderId); + Optional findFirstByOrderIdAndStatusOrderByIdDesc(Long orderId, PaymentStatus status); } diff --git a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java index 30f8bb6..22e9edf 100644 --- a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java +++ b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java @@ -14,6 +14,10 @@ import java.util.Optional; public interface PaymentTransactionRepository extends JpaRepository, JpaSpecificationExecutor { List findByGatewayTransactionId(String gatewayTransactionId); Optional findByIdempotencyKey(String idempotencyKey); + Optional findByPaymentIdAndType( + Long paymentId, + PaymentTransactionType type + ); Optional findFirstByPaymentIdAndTypeAndStatusOrderByIdDesc( Long paymentId, PaymentTransactionType type, diff --git a/src/main/java/com/imprimelibros/erp/pedidos/Pedido.java b/src/main/java/com/imprimelibros/erp/pedidos/Pedido.java index b89ca3f..58e6b3d 100644 --- a/src/main/java/com/imprimelibros/erp/pedidos/Pedido.java +++ b/src/main/java/com/imprimelibros/erp/pedidos/Pedido.java @@ -1,11 +1,15 @@ package com.imprimelibros.erp.pedidos; import jakarta.persistence.*; -import java.time.LocalDateTime; + +import java.util.ArrayList; +import java.util.List; + +import com.imprimelibros.erp.common.jpa.AbstractAuditedEntity; @Entity @Table(name = "pedidos") -public class Pedido { +public class Pedido extends AbstractAuditedEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -37,27 +41,8 @@ public class Pedido { @Column(name = "proveedor_ref", length = 100) private String proveedorRef; - // Auditoría básica (coincidiendo con las columnas que se ven en la captura) - @Column(name = "created_by") - private Long createdBy; - - @Column(name = "updated_by") - private Long updatedBy; - - @Column(name = "deleted_by") - private Long deletedBy; - - @Column(name = "deleted", nullable = false) - private boolean deleted = false; - - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - @Column(name = "deleted_at") - private LocalDateTime deletedAt; + @OneToMany(mappedBy = "pedido", cascade = CascadeType.ALL, orphanRemoval = false) + private List lineas = new ArrayList<>(); // --- Getters y setters --- @@ -132,60 +117,4 @@ public class Pedido { public void setProveedorRef(String proveedorRef) { this.proveedorRef = proveedorRef; } - - public Long getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(Long createdBy) { - this.createdBy = createdBy; - } - - public Long getUpdatedBy() { - return updatedBy; - } - - public void setUpdatedBy(Long updatedBy) { - this.updatedBy = updatedBy; - } - - public Long getDeletedBy() { - return deletedBy; - } - - public void setDeletedBy(Long deletedBy) { - this.deletedBy = deletedBy; - } - - public boolean isDeleted() { - return deleted; - } - - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - public LocalDateTime getDeletedAt() { - return deletedAt; - } - - public void setDeletedAt(LocalDateTime deletedAt) { - this.deletedAt = deletedAt; - } } diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccion.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccion.java new file mode 100644 index 0000000..40047d3 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccion.java @@ -0,0 +1,308 @@ +package com.imprimelibros.erp.pedidos; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import com.imprimelibros.erp.direcciones.Direccion.TipoIdentificacionFiscal; +import com.imprimelibros.erp.paises.Paises; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@Entity +@Table(name = "pedidos_direcciones") +public class PedidoDireccion { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // FK a pedidos_lineas.id (nullable, on delete set null) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pedido_linea_id") + private PedidoLinea pedidoLinea; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pedido_id") + private Pedido pedido; + + @Column(name = "unidades") + private Integer unidades; + + @Column(name = "is_facturacion", nullable = false) + private boolean facturacion = false; + + @Column(name = "is_ejemplar_prueba", nullable = false) + private boolean ejemplarPrueba = false; + + @Column(name = "email", length = 255) + private String email; + + @Column(name = "att", nullable = false, length = 150) + private String att; + + @Column(name = "direccion", nullable = false, length = 255) + private String direccion; + + @Column(name = "cp", nullable = false) + private Integer cp; + + @Column(name = "ciudad", nullable = false, length = 100) + private String ciudad; + + @Column(name = "provincia", nullable = false, length = 100) + private String provincia; + + @Column(name = "pais_code3", nullable = false, length = 3) + private String paisCode3 = "esp"; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pais_code3", referencedColumnName = "code3", insertable = false, updatable = false) + private Paises pais; + + @Transient + private String paisNombre; + + @Column(name = "telefono", nullable = false, length = 30) + private String telefono; + + @Column(name = "instrucciones", length = 255) + private String instrucciones; + + @Column(name = "razon_social", length = 150) + private String razonSocial; + + @Enumerated(EnumType.STRING) + @Column(name = "tipo_identificacion_fiscal", nullable = false, length = 20) + private TipoIdentificacionFiscal tipoIdentificacionFiscal = TipoIdentificacionFiscal.DNI; + + @Column(name = "identificacion_fiscal", length = 50) + private String identificacionFiscal; + + @Column(name = "is_palets", nullable = false) + private boolean palets = false; + + @CreationTimestamp + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + // ===== GETTERS & SETTERS ===== + + public Long getId() { + return id; + } + + public PedidoLinea getPedidoLinea() { + return pedidoLinea; + } + + public void setPedidoLinea(PedidoLinea pedidoLinea) { + this.pedidoLinea = pedidoLinea; + } + + public Pedido getPedido() { + return pedido; + } + + public void setPedido(Pedido pedido) { + this.pedido = pedido; + } + + public Integer getUnidades() { + return unidades; + } + + public void setUnidades(Integer unidades) { + this.unidades = unidades; + } + + public boolean isFacturacion() { + return facturacion; + } + + public void setFacturacion(boolean facturacion) { + this.facturacion = facturacion; + } + + public boolean isEjemplarPrueba() { + return ejemplarPrueba; + } + + public void setEjemplarPrueba(boolean ejemplarPrueba) { + this.ejemplarPrueba = ejemplarPrueba; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getAtt() { + return att; + } + + public void setAtt(String att) { + this.att = att; + } + + public String getDireccion() { + return direccion; + } + + public void setDireccion(String direccion) { + this.direccion = direccion; + } + + public Integer getCp() { + return cp; + } + + public void setCp(Integer cp) { + this.cp = cp; + } + + public String getCiudad() { + return ciudad; + } + + public void setCiudad(String ciudad) { + this.ciudad = ciudad; + } + + public String getProvincia() { + return provincia; + } + + public void setProvincia(String provincia) { + this.provincia = provincia; + } + + public String getPaisCode3() { + return paisCode3; + } + + public void setPaisCode3(String paisCode3) { + this.paisCode3 = paisCode3; + } + + public Paises getPais() { + return pais; + } + + public void setPais(Paises pais) { + this.pais = pais; + } + + public String getTelefono() { + return telefono; + } + + public void setTelefono(String telefono) { + this.telefono = telefono; + } + + public String getInstrucciones() { + return instrucciones; + } + + public void setInstrucciones(String instrucciones) { + this.instrucciones = instrucciones; + } + + public String getRazonSocial() { + return razonSocial; + } + + public void setRazonSocial(String razonSocial) { + this.razonSocial = razonSocial; + } + + public TipoIdentificacionFiscal getTipoIdentificacionFiscal() { + return tipoIdentificacionFiscal; + } + + public void setTipoIdentificacionFiscal(TipoIdentificacionFiscal tipoIdentificacionFiscal) { + this.tipoIdentificacionFiscal = tipoIdentificacionFiscal; + } + + public String getIdentificacionFiscal() { + return identificacionFiscal; + } + + public void setIdentificacionFiscal(String identificacionFiscal) { + this.identificacionFiscal = identificacionFiscal; + } + + public boolean isPalets() { + return palets; + } + + public void setPalets(boolean palets) { + this.palets = palets; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public String getPaisNombre() { + return paisNombre; + } + + public void setPaisNombre(String paisNombre) { + this.paisNombre = paisNombre; + } + + + public Map toSkMap(Double pesoKg) { + + Map direccion = new HashMap<>(); + direccion.put("cantidad", this.getUnidades()); + direccion.put("peso", pesoKg); + direccion.put("att", this.getAtt()); + direccion.put("email", this.getEmail()); + direccion.put("direccion", this.getDireccion()); + direccion.put("pais_code3", this.getPaisCode3()); + direccion.put("cp", this.getCp()); + direccion.put("municipio", this.getCiudad()); + direccion.put("provincia", this.getProvincia()); + direccion.put("telefono", this.getTelefono()); + direccion.put("entregaPieCalle", this.isPalets() ? 1 : 0); + direccion.put("is_ferro_prototipo", this.isEjemplarPrueba() ? 1 : 0); + direccion.put("num_ferro_prototipo", this.isEjemplarPrueba() ? 1 : 0); + + Map map = new HashMap<>(); + map.put("direccion", direccion); + map.put("unidades", this.getUnidades()); + map.put("entregaPalets", this.isPalets() ? 1 : 0); + + return map; + } + + public static Map toSkMapDepositoLegal() { + Map direccion = new HashMap<>(); + direccion.put("cantidad", 4); + direccion.put("peso", 0); + direccion.put("att", "Unidades para Depósito Legal (sin envío)"); + direccion.put("email", ""); + direccion.put("direccion", ""); + direccion.put("pais_code3", "esp"); + direccion.put("cp", ""); + direccion.put("municipio", ""); + direccion.put("provincia", ""); + direccion.put("telefono", ""); + direccion.put("entregaPieCalle", 0); + direccion.put("is_ferro_prototipo", 0); + direccion.put("num_ferro_prototipo", 0); + + Map map = new HashMap<>(); + map.put("direccion", direccion); + map.put("unidades", 4); + map.put("entregaPalets", 0); + + return map; + } +} \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccionRepository.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccionRepository.java new file mode 100644 index 0000000..fc67cff --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoDireccionRepository.java @@ -0,0 +1,26 @@ +package com.imprimelibros.erp.pedidos; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PedidoDireccionRepository extends JpaRepository { + + // Todas las direcciones de una línea de pedido + List findByPedidoLinea_Id(Long pedidoLineaId); + + // Si en tu código sueles trabajar con el objeto: + List findByPedidoLinea(PedidoLinea pedidoLinea); + + PedidoDireccion findByPedidoIdAndFacturacionTrue(Long pedidoId); + + @Query(""" + select distinct d + from PedidoDireccion d + join d.pedidoLinea pl + where d.pedidoLinea.id = :pedidoLineaId + """) + List findByPedidoLineaId(Long pedidoLineaId); + +} diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java index f19f72a..8531d2b 100644 --- a/src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java @@ -9,6 +9,36 @@ import com.imprimelibros.erp.presupuesto.dto.Presupuesto; @Table(name = "pedidos_lineas") public class PedidoLinea { + public enum Estado { + pendiente_pago("pedido.estado.pendiente_pago", 1), + procesando_pago("pedido.estado.procesando_pago", 2), + denegado_pago("pedido.estado.denegado_pago", 3), + aprobado("pedido.estado.aprobado", 4), + maquetacion("pedido.estado.maquetacion", 5), + haciendo_ferro("pedido.estado.haciendo_ferro", 6), + esperando_aceptacion_ferro("pedido.estado.esperando_aceptacion_ferro", 7), + ferro_cliente("pedido.estado.ferro_cliente", 8), + produccion("pedido.estado.produccion", 9), + terminado("pedido.estado.terminado", 10), + cancelado("pedido.estado.cancelado", 11); + + private final String messageKey; + private final int priority; + + Estado(String messageKey, int priority) { + this.messageKey = messageKey; + this.priority = priority; + } + + public String getMessageKey() { + return messageKey; + } + + public int getPriority() { + return priority; + } + } + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -21,6 +51,16 @@ public class PedidoLinea { @JoinColumn(name = "presupuesto_id", nullable = false) private Presupuesto presupuesto; + @Enumerated(EnumType.STRING) + @Column(name = "estado", nullable = false) + private Estado estado = Estado.aprobado; + + @Column(name = "estado_manual", nullable = false) + private Boolean estadoManual; + + @Column(name = "fecha_entrega") + private LocalDateTime fechaEntrega; + @Column(name = "created_at") private LocalDateTime createdAt; @@ -53,6 +93,30 @@ public class PedidoLinea { this.presupuesto = presupuesto; } + public Estado getEstado() { + return estado; + } + + public void setEstado(Estado estado) { + this.estado = estado; + } + + public Boolean getEstadoManual() { + return estadoManual; + } + + public void setEstadoManual(Boolean estadoManual) { + this.estadoManual = estadoManual; + } + + public LocalDateTime getFechaEntrega() { + return fechaEntrega; + } + + public void setFechaEntrega(LocalDateTime fechaEntrega) { + this.fechaEntrega = fechaEntrega; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoLineaRepository.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoLineaRepository.java index d4e8333..b18fa87 100644 --- a/src/main/java/com/imprimelibros/erp/pedidos/PedidoLineaRepository.java +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoLineaRepository.java @@ -9,6 +9,7 @@ import java.util.List; public interface PedidoLineaRepository extends JpaRepository { List findByPedidoId(Long pedidoId); + List findByPedidoIdOrderByIdAsc(Long pedidoId); List findByPresupuestoId(Long presupuestoId); } diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoRepository.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoRepository.java index 6f19f9d..46f2856 100644 --- a/src/main/java/com/imprimelibros/erp/pedidos/PedidoRepository.java +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoRepository.java @@ -1,10 +1,23 @@ package com.imprimelibros.erp.pedidos; +import java.time.Instant; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository -public interface PedidoRepository extends JpaRepository { - // aquí podrás añadir métodos tipo: - // List findByDeletedFalse(); +public interface PedidoRepository extends JpaRepository, JpaSpecificationExecutor { + + // Suma de "total" para un "createdBy" desde una fecha dada + @Query(""" + SELECT COALESCE(SUM(p.total), 0) + FROM Pedido p + WHERE p.createdBy.id = :userId + AND p.createdAt >= :afterDate + """) + Double sumTotalByCreatedByAndCreatedAtAfter(@Param("userId") Long userId, + @Param("afterDate") Instant afterDate); } diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java index 3150da0..f0e0478 100644 --- a/src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java @@ -1,14 +1,30 @@ package com.imprimelibros.erp.pedidos; +import java.time.Instant; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import org.springframework.context.MessageSource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.imprimelibros.erp.cart.Cart; +import com.imprimelibros.erp.cart.CartDireccion; +import com.imprimelibros.erp.cart.CartItem; +import com.imprimelibros.erp.cart.CartService; +import com.imprimelibros.erp.common.Utils; +import com.imprimelibros.erp.direcciones.Direccion; import com.imprimelibros.erp.presupuesto.PresupuestoRepository; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; +import com.imprimelibros.erp.presupuesto.service.PresupuestoService; +import com.imprimelibros.erp.users.UserService; +import com.imprimelibros.erp.direcciones.DireccionService; +import com.imprimelibros.erp.externalApi.skApiClient; +import com.imprimelibros.erp.pedidos.PedidoLinea.Estado; @Service public class PedidoService { @@ -16,50 +32,45 @@ public class PedidoService { private final PedidoRepository pedidoRepository; private final PedidoLineaRepository pedidoLineaRepository; private final PresupuestoRepository presupuestoRepository; + private final PedidoDireccionRepository pedidoDireccionRepository; + private final DireccionService direccionService; + private final UserService userService; + private final PresupuestoService presupuestoService; + private final CartService cartService; + private final skApiClient skApiClient; + private final PresupuestoRepository presupuestoRepo; + private final MessageSource messageSource; public PedidoService(PedidoRepository pedidoRepository, PedidoLineaRepository pedidoLineaRepository, - PresupuestoRepository presupuestoRepository) { + PresupuestoRepository presupuestoRepository, PedidoDireccionRepository pedidoDireccionRepository, + DireccionService direccionService, UserService userService, PresupuestoService presupuestoService, + CartService cartService, skApiClient skApiClient, PresupuestoRepository presupuestoRepo, + MessageSource messageSource) { this.pedidoRepository = pedidoRepository; this.pedidoLineaRepository = pedidoLineaRepository; this.presupuestoRepository = presupuestoRepository; + this.pedidoDireccionRepository = pedidoDireccionRepository; + this.direccionService = direccionService; + this.userService = userService; + this.presupuestoService = presupuestoService; + this.cartService = cartService; + this.skApiClient = skApiClient; + this.presupuestoRepo = presupuestoRepo; + this.messageSource = messageSource; } - public int getDescuentoFidelizacion() { - // descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el - // ultimo año) - double totalGastado = 1600.0; // Ejemplo, deberías obtenerlo del historial del cliente - if (totalGastado < 1200) { - return 0; - } else if (totalGastado >= 1200 && totalGastado < 1999) { - return 1; - } else if (totalGastado >= 2000 && totalGastado < 2999) { - return 2; - } else if (totalGastado >= 3000 && totalGastado < 3999) { - return 3; - } else if (totalGastado >= 4000 && totalGastado < 4999) { - return 4; - } else if (totalGastado >= 5000) { - return 5; - } - return 0; - } - - /** - * Crea un pedido a partir de: - * - lista de IDs de presupuesto - * - resumen numérico del carrito (getCartSummaryRaw) - * - datos de proveedor - * - usuario que crea el pedido - */ @Transactional - public Pedido crearPedido(List presupuestoIds, - Map cartSummaryRaw, + public Pedido crearPedido( + Long cartId, + Long direccionFacturacionId, String proveedor, - String proveedorRef, - Long userId) { + String proveedorRef) { Pedido pedido = new Pedido(); + Cart cart = cartService.getCartById(cartId); + Map cartSummaryRaw = cartService.getCartSummaryRaw(cart, Locale.getDefault()); + // Datos económicos (ojo con las claves, son las del summaryRaw) pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d)); pedido.setEnvio((Double) cartSummaryRaw.getOrDefault("shipment", 0.0d)); @@ -69,33 +80,567 @@ public class PedidoService { pedido.setTotal((Double) cartSummaryRaw.getOrDefault("total", 0.0d)); // Proveedor - pedido.setProveedor(proveedor); - pedido.setProveedorRef(proveedorRef); + if (proveedor != null && proveedorRef != null) { + pedido.setProveedor(proveedor); + pedido.setProveedorRef(proveedorRef); + } // Auditoría mínima - pedido.setCreatedBy(userId); - pedido.setCreatedAt(LocalDateTime.now()); + Long userId = cart.getUserId(); + pedido.setCreatedBy(userService.findById(userId)); + pedido.setCreatedAt(Instant.now()); pedido.setDeleted(false); - pedido.setUpdatedAt(LocalDateTime.now()); - pedido.setUpdatedBy(userId); + pedido.setUpdatedAt(Instant.now()); + pedido.setUpdatedBy(userService.findById(userId)); // Guardamos el pedido - Pedido saved = pedidoRepository.save(pedido); + Pedido pedidoGuardado = pedidoRepository.save(pedido); - // Crear líneas del pedido - for (Long presupuestoId : presupuestoIds) { - Presupuesto presupuesto = presupuestoRepository.getReferenceById(presupuestoId); + List items = cart.getItems(); + + for (Integer i = 0; i < items.size(); i++) { + CartItem item = items.get(i); + Presupuesto pCart = item.getPresupuesto(); + + // Asegurarnos de trabajar con la entidad gestionada por JPA + Presupuesto p = presupuestoRepository.findById(pCart.getId()) + .orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId())); + p.setEstado(Presupuesto.Estado.aceptado); + presupuestoRepository.save(p); PedidoLinea linea = new PedidoLinea(); - linea.setPedido(saved); - linea.setPresupuesto(presupuesto); + linea.setPedido(pedidoGuardado); + linea.setPresupuesto(p); linea.setCreatedBy(userId); linea.setCreatedAt(LocalDateTime.now()); + linea.setEstado(PedidoLinea.Estado.pendiente_pago); + linea.setEstadoManual(false); + pedidoLineaRepository.save(linea); + // Guardar las direcciones asociadas a la línea del pedido + Map direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p); + saveDireccionesPedidoLinea(direcciones_presupuesto, pedidoGuardado, linea, direccionFacturacionId); + + } + + return pedidoGuardado; + } + + public Boolean markPedidoAsProcesingPayment(Long pedidoId) { + Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null); + if (pedido == null) { + return false; + } + List lineas = pedidoLineaRepository.findByPedidoId(pedidoId); + for (PedidoLinea linea : lineas) { + linea.setEstado(PedidoLinea.Estado.procesando_pago); pedidoLineaRepository.save(linea); } - return saved; + return true; + } + + public Boolean markPedidoAsPaymentDenied(Long pedidoId) { + Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null); + if (pedido == null) { + return false; + } + List lineas = pedidoLineaRepository.findByPedidoId(pedidoId); + for (PedidoLinea linea : lineas) { + linea.setEstado(PedidoLinea.Estado.denegado_pago); + pedidoLineaRepository.save(linea); + } + + return true; + } + + public Pedido findById(Long pedidoId) { + return pedidoRepository.findById(pedidoId).orElse(null); + } + + /** Lista de los items del pedido preparados para la vista */ + @Transactional + public List> getLineas(Long pedidoId, Locale locale) { + Pedido p = pedidoRepository.findById(pedidoId).orElse(null); + if (p == null) { + return new ArrayList<>(); + } + + List> resultados = new ArrayList<>(); + List items = pedidoLineaRepository.findByPedidoIdOrderByIdAsc(p.getId()); + for (PedidoLinea item : items) { + + Presupuesto presupuesto = item.getPresupuesto(); + Map elemento = presupuestoService.getPresupuestoInfoForCard(presupuesto, locale); + elemento.put("estado", item.getEstado()); + elemento.put("fechaEntrega", + item.getFechaEntrega() != null ? Utils.formatDate(item.getFechaEntrega(), locale) : ""); + elemento.put("lineaId", item.getId()); + resultados.add(elemento); + } + return resultados; + } + + public PedidoDireccion getDireccionFacturacionPedido(Long pedidoId) { + return pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId); + } + + public List getDireccionesEntregaPedidoLinea(Long pedidoLineaId) { + return pedidoDireccionRepository.findByPedidoLinea_Id(pedidoLineaId); + } + + public Boolean setOrderAsPaid(Long pedidoId) { + Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null); + if (pedido == null) { + return false; + } + List lineas = pedidoLineaRepository.findByPedidoId(pedidoId); + List> referenciasProveedor = new ArrayList<>(); + Integer total = lineas.size(); + Integer counter = 1; + for (PedidoLinea linea : lineas) { + if (linea.getEstado() == Estado.pendiente_pago + || linea.getEstado() == Estado.denegado_pago) { + + Presupuesto presupuesto = linea.getPresupuesto(); + linea.setEstado(getEstadoInicial(presupuesto)); + pedidoLineaRepository.save(linea); + + // Save presupuesto in SK + Map result = savePresupuestoSK(linea.getId(), presupuesto, counter, total); + if (result == null) { + return false; + } + referenciasProveedor.add(result); + counter++; + } + } + + if (referenciasProveedor.isEmpty()) { + return false; + } + + // Save pedido in SK + ArrayList presupuestoSkIds = new ArrayList<>(); + for (Map presData : referenciasProveedor) { + Long presId = ((Number) presData.get("id")).longValue(); + presupuestoSkIds.add(presId); + } + + Map ids = new HashMap<>(); + ids.put("presupuesto_ids", presupuestoSkIds); + Long skPedidoId = skApiClient.crearPedido(ids); + if (skPedidoId == null) { + System.out.println("No se pudo crear el pedido en SK."); + return false; + } + pedido.setProveedor("Safekat"); + pedido.setProveedorRef(skPedidoId.toString()); + pedidoRepository.save(pedido); + return true; + } + + public Map actualizarEstado(Long pedidoLineaId, Locale locale) { + PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); + if (pedidoLinea == null) { + return Map.of("success", false, + "message", messageSource.getMessage("pedido.errors.linea-not-found", null, locale)); + } + + if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.haciendo_ferro.getPriority() && + pedidoLinea.getEstado().getPriority() < PedidoLinea.Estado.terminado.getPriority()) { + PedidoLinea.Estado estadoOld = pedidoLinea.getEstado(); + Map result = skApiClient.checkPedidoEstado( + Long.valueOf(pedidoLinea.getPresupuesto().getProveedorRef2().toString()), locale); + if (result == null || !result.containsKey("estado")) { + return Map.of( + "success", false, + "message", messageSource.getMessage("pedido.errors.update-server-error", null, locale)); + } + PedidoLinea.Estado estadoSk = PedidoLinea.Estado.valueOf((String) result.get("estado")); + if (estadoOld == estadoSk) { + return Map.of( + "success", true, + "state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale), + "stateKey", estadoSk.name(), + "message", messageSource.getMessage("pedido.success.same-estado", null, locale)); + } + + pedidoLinea.setEstado(estadoSk); + pedidoLineaRepository.save(pedidoLinea); + return Map.of( + "success", true, + "state", messageSource.getMessage("pedido.estado." + estadoSk.name(), null, locale), + "stateKey", estadoSk.name(), + "message", messageSource.getMessage("pedido.success.estado-actualizado", null, locale)); + } + + return Map.of( + "success", false, + "message", messageSource.getMessage("pedido.errors.cannot-update", null, locale)); + } + + public Boolean markPedidoAsMaquetacionDone(Long pedidoId) { + Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null); + if (pedido == null) { + return false; + } + List lineas = pedidoLineaRepository.findByPedidoId(pedidoId); + for (PedidoLinea linea : lineas) { + if (linea.getEstado() == Estado.maquetacion) { + linea.setEstado(Estado.haciendo_ferro); + pedidoLineaRepository.save(linea); + } + } + + return true; + } + + public Map getFilesType(Long pedidoLineaId, Locale locale) { + PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); + if (pedidoLinea == null) { + return Map.of("success", false, "message", "Línea de pedido no encontrada."); + } + Map files = skApiClient.getFilesTypes( + Long.valueOf(pedidoLinea.getPresupuesto().getProveedorRef2().toString()), locale); + return files; + } + + + public byte[] getFerroFileContent(Long pedidoLineaId, Locale locale) { + return downloadFile(pedidoLineaId, "ferro", locale); + } + + public byte[] getCubiertaFileContent(Long pedidoLineaId, Locale locale) { + return downloadFile(pedidoLineaId, "cubierta", locale); + } + + public byte[] getTapaFileContent(Long pedidoLineaId, Locale locale) { + return downloadFile(pedidoLineaId, "tapa", locale); + } + + public Boolean aceptarFerro(Long pedidoLineaId, Locale locale) { + PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); + if (pedidoLinea == null) { + return false; + } + if (pedidoLinea.getEstado() != PedidoLinea.Estado.esperando_aceptacion_ferro) { + return false; + } + Boolean result = skApiClient.aceptarFerro( + Long.valueOf(pedidoLinea.getPresupuesto().getProveedorRef2().toString()), locale); + if (!result) { + return false; + } + pedidoLinea.setEstado(PedidoLinea.Estado.produccion); + pedidoLineaRepository.save(pedidoLinea); + return true; + } + /*************************** + * MÉTODOS PRIVADOS + ***************************/ + private byte[] downloadFile(Long pedidoLineaId, String fileType, Locale locale) { + PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); + if (pedidoLinea == null) { + return null; + } + byte[] fileData = skApiClient.downloadFile( + Long.valueOf(pedidoLinea.getPresupuesto().getProveedorRef2().toString()), + fileType, + locale); + return fileData; + } + + @Transactional + private Map savePresupuestoSK(Long pedidoLineaId, Presupuesto presupuesto, Integer counter, + Integer total) { + + Map data_to_send = presupuestoService.toSkApiRequest(presupuesto, true); + data_to_send.put("createPedido", 0); + + // Recuperar el mapa anidado datosCabecera + @SuppressWarnings("unchecked") + Map datosCabecera = (Map) data_to_send.get("datosCabecera"); + if (datosCabecera != null) { + Object tituloOriginal = datosCabecera.get("titulo"); + datosCabecera.put( + "titulo", + "[" + (counter) + "/" + total + "] " + (tituloOriginal != null ? tituloOriginal : "")); + } + + List direccionesPedidoLinea = pedidoDireccionRepository + .findByPedidoLineaId(pedidoLineaId); + List> direccionesPresupuesto = new ArrayList<>(); + List> direccionEjemplarPrueba = new ArrayList<>(); + + for (PedidoDireccion pd : direccionesPedidoLinea) { + if (pd.isEjemplarPrueba()) { + direccionEjemplarPrueba.add( + pd.toSkMap(presupuesto.getPeso())); + } else { + direccionesPresupuesto.add( + pd.toSkMap(presupuesto.getPeso() * pd.getUnidades())); + } + + } + if (presupuesto.getServiciosJson() != null && presupuesto.getServiciosJson().contains("deposito-legal")) { + direccionesPresupuesto.add( + PedidoDireccion.toSkMapDepositoLegal()); + } + data_to_send.put("direcciones", direccionesPresupuesto); + if (direccionEjemplarPrueba.size() > 0) + data_to_send.put("direccionesFP1", direccionEjemplarPrueba.get(0)); + else { + data_to_send.put("direccionesFP1", new ArrayList<>()); + } + + Map result = skApiClient.savePresupuesto(data_to_send); + + if (result.containsKey("error")) { + System.out.println("Error al guardar presupuesto en SK"); + System.out.println("-------------------------"); + System.out.println(result.get("error")); + // decide si seguir con otros items o abortar: + // continue; o bien throw ... + return null; + } + + Object dataObj = result.get("data"); + if (!(dataObj instanceof Map dataRaw)) { + System.out.println("Formato inesperado de 'data' en savePresupuesto: " + result); + return null; + } + + @SuppressWarnings("unchecked") + Map dataMap = (Map) dataRaw; + Long presId = ((Number) dataMap.get("id")).longValue(); + String skin = ((String) dataMap.get("iskn")).toString(); + presupuesto.setProveedor("Safekat"); + presupuesto.setProveedorRef1(skin); + presupuesto.setProveedorRef2(presId); + presupuesto.setEstado(Presupuesto.Estado.aceptado); + presupuestoRepo.save(presupuesto); + + return dataMap; + } + + // Obtener las direcciones de envío asociadas a un presupuesto en el carrito + private Map getDireccionesPresupuesto(Cart cart, Presupuesto presupuesto) { + + List> direccionesPresupuesto = new ArrayList<>(); + List> direccionesPrueba = new ArrayList<>(); + if (cart.getOnlyOneShipment()) { + List direcciones = cart.getDirecciones().stream().limit(1).toList(); + if (!direcciones.isEmpty()) { + if (presupuesto.getServiciosJson() != null + && presupuesto.getServiciosJson().contains("deposito-legal")) { + direccionesPresupuesto.add(direcciones.get(0).toSkMap( + presupuesto.getSelectedTirada(), + presupuesto.getPeso(), + direcciones.get(0).getIsPalets(), + false)); + + direccionesPresupuesto.add(direcciones.get(0).toSkMapDepositoLegal()); + } else { + direccionesPresupuesto.add(direcciones.get(0).toSkMap( + presupuesto.getSelectedTirada(), + presupuesto.getPeso(), + direcciones.get(0).getIsPalets(), + false)); + } + if (presupuesto.getServiciosJson() != null + && presupuesto.getServiciosJson().contains("ejemplar-prueba")) { + direccionesPrueba.add(direcciones.get(0).toSkMap( + 1, + presupuesto.getPeso(), + false, + true)); + } + + Map direccionesRet = new HashMap<>(); + direccionesRet.put("direcciones", direccionesPresupuesto); + if (!direccionesPrueba.isEmpty()) + direccionesRet.put("direccionesFP1", direccionesPrueba.get(0)); + else { + direccionesRet.put("direccionesFP1", new ArrayList<>()); + } + return direccionesRet; + } + } else { + List direcciones = cart.getDirecciones().stream() + .filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(presupuesto.getId())) + .toList(); + + for (CartDireccion cd : direcciones) { + + // direccion de ejemplar de prueba + if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) { + continue; + } + if (cd.getUnidades() == null || cd.getUnidades() <= 0) { + direccionesPrueba.add(cd.toSkMap( + 1, + presupuesto.getPeso(), + false, + true)); + } else { + direccionesPresupuesto.add(cd.toSkMap( + cd.getUnidades(), + presupuesto.getPeso(), + cd.getIsPalets(), + false)); + } + } + if (presupuesto.getServiciosJson() != null + && presupuesto.getServiciosJson().contains("deposito-legal")) { + CartDireccion cd = new CartDireccion(); + direccionesPresupuesto.add(cd.toSkMapDepositoLegal()); + } + } + Map direccionesRet = new HashMap<>(); + direccionesRet.put("direcciones", direccionesPresupuesto); + if (!direccionesPrueba.isEmpty()) + direccionesRet.put("direccionesFP1", direccionesPrueba.get(0)); + else { + direccionesRet.put("direccionesFP1", new ArrayList<>()); + } + return direccionesRet; + } + + @Transactional + private void saveDireccionesPedidoLinea( + Map direcciones, + Pedido pedido, + PedidoLinea linea, Long direccionFacturacionId) { + + String email = pedido.getCreatedBy().getUserName(); + + // direccion prueba + if (direcciones.containsKey("direccionesFP1")) { + + try { + @SuppressWarnings("unchecked") + Map fp1 = (Map) direcciones.get("direccionesFP1"); + @SuppressWarnings("unchecked") + PedidoDireccion direccion = saveDireccion( + email, + false, + (HashMap) fp1.get("direccion"), + pedido, + linea, + true, + false); + pedidoDireccionRepository.save(direccion); + } catch (Exception e) { + // Viene vacio + } + } + if (direcciones.containsKey("direcciones")) { + List dirs = (List) direcciones.get("direcciones"); + for (Object dir : dirs) { + @SuppressWarnings("unchecked") + HashMap direccionEnvio = (HashMap) ((HashMap) dir) + .get("direccion"); + if (direccionEnvio.get("cantidad") != null && (Integer) direccionEnvio.get("cantidad") == 4 + && direccionEnvio.get("att").toString().contains("Depósito Legal")) { + continue; // Saltar la dirección de depósito legal + } + @SuppressWarnings("unchecked") + PedidoDireccion direccion = saveDireccion( + email, + ((Number) ((HashMap) dir) + .getOrDefault("entregaPalets", 0)) + .intValue() == 1, + (HashMap) ((HashMap) dir).get("direccion"), + pedido, + linea, false, + false); + pedidoDireccionRepository.save(direccion); + } + } + if (direccionFacturacionId != null) { + Direccion dirFact = direccionService.findById(direccionFacturacionId).orElse(null); + if (dirFact != null) { + HashMap dirFactMap = new HashMap<>(); + dirFactMap.put("att", dirFact.getAtt()); + dirFactMap.put("direccion", dirFact.getDireccion()); + dirFactMap.put("cp", dirFact.getCp()); + dirFactMap.put("municipio", dirFact.getCiudad()); + dirFactMap.put("provincia", dirFact.getProvincia()); + dirFactMap.put("pais_code3", dirFact.getPaisCode3()); + dirFactMap.put("telefono", dirFact.getTelefono()); + dirFactMap.put("instrucciones", dirFact.getInstrucciones()); + dirFactMap.put("razon_social", dirFact.getRazonSocial()); + dirFactMap.put("tipo_identificacion_fiscal", dirFact.getTipoIdentificacionFiscal().name()); + dirFactMap.put("identificacion_fiscal", dirFact.getIdentificacionFiscal()); + + PedidoDireccion direccion = saveDireccion( + email, + false, + dirFactMap, + pedido, + linea, + false, + true); + pedidoDireccionRepository.save(direccion); + } + } + } + + private PedidoDireccion saveDireccion( + String email, + Boolean palets, + HashMap dir, + Pedido pedido, + PedidoLinea linea, + Boolean isEjemplarPrueba, + Boolean isFacturacion) { + + PedidoDireccion direccion = new PedidoDireccion(); + direccion.setEmail(email); + direccion.setPalets(isEjemplarPrueba || isFacturacion ? false : palets); + direccion.setPedidoLinea(isFacturacion ? null : linea); + if (isFacturacion) { + direccion.setUnidades(null); + direccion.setFacturacion(true); + direccion.setPedido(pedido); + + } else { + if (isEjemplarPrueba) { + direccion.setUnidades(1); + direccion.setEjemplarPrueba(true); + } else { + direccion.setUnidades((Integer) dir.getOrDefault("cantidad", 1)); + direccion.setEjemplarPrueba(false); + } + direccion.setFacturacion(false); + } + + direccion.setAtt((String) dir.getOrDefault("att", "")); + direccion.setDireccion((String) dir.getOrDefault("direccion", "")); + direccion.setCp((Integer) dir.getOrDefault("cp", 0)); + direccion.setCiudad((String) dir.getOrDefault("municipio", "")); + direccion.setProvincia((String) dir.getOrDefault("provincia", "")); + direccion.setPaisCode3((String) dir.getOrDefault("pais_code3", "esp")); + direccion.setTelefono((String) dir.getOrDefault("telefono", "")); + direccion.setInstrucciones((String) dir.getOrDefault("instrucciones", "")); + direccion.setRazonSocial((String) dir.getOrDefault("razon_social", "")); + direccion.setTipoIdentificacionFiscal(Direccion.TipoIdentificacionFiscal + .valueOf((String) dir.getOrDefault("tipo_identificacion_fiscal", + Direccion.TipoIdentificacionFiscal.DNI.name()))); + direccion.setIdentificacionFiscal((String) dir.getOrDefault("identificacion_fiscal", "")); + + return direccion; + + } + + private Estado getEstadoInicial(Presupuesto p) { + + if (presupuestoService.hasMaquetacion(p)) { + return Estado.maquetacion; + } else { + return Estado.haciendo_ferro; + } } } diff --git a/src/main/java/com/imprimelibros/erp/pedidos/PedidosController.java b/src/main/java/com/imprimelibros/erp/pedidos/PedidosController.java new file mode 100644 index 0000000..8b4d5bc --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/pedidos/PedidosController.java @@ -0,0 +1,402 @@ +package com.imprimelibros.erp.pedidos; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import java.security.Principal; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.springframework.context.MessageSource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; + +import com.imprimelibros.erp.common.Utils; +import com.imprimelibros.erp.datatables.DataTable; +import com.imprimelibros.erp.datatables.DataTablesParser; +import com.imprimelibros.erp.datatables.DataTablesRequest; +import com.imprimelibros.erp.datatables.DataTablesResponse; +import com.imprimelibros.erp.i18n.TranslationService; +import com.imprimelibros.erp.paises.PaisesService; +import com.imprimelibros.erp.presupuesto.service.PresupuestoService; +import com.imprimelibros.erp.users.UserDao; + +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@Controller +@RequestMapping("/pedidos") +public class PedidosController { + + private final PresupuestoService presupuestoService; + + private final PedidoRepository repoPedido; + private final PedidoService pedidoService; + private final UserDao repoUser; + private final MessageSource messageSource; + private final PedidoLineaRepository repoPedidoLinea; + private final PaisesService paisesService; + private final TranslationService translationService; + + public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser, + MessageSource messageSource, TranslationService translationService, + PedidoLineaRepository repoPedidoLinea, PaisesService paisesService, PresupuestoService presupuestoService) { + this.repoPedido = repoPedido; + this.pedidoService = pedidoService; + this.repoUser = repoUser; + this.messageSource = messageSource; + this.translationService = translationService; + this.repoPedidoLinea = repoPedidoLinea; + this.paisesService = paisesService; + this.presupuestoService = presupuestoService; + } + + @GetMapping + public String listarPedidos(Model model, Locale locale) { + + List keys = List.of( + "app.cancelar", + "app.seleccionar", + "app.yes", + "checkout.payment.card", + "checkout.payment.bizum", + "checkout.payment.bank-transfer", + "checkout.error.select-method"); + + Map translations = translationService.getTranslations(locale, keys); + model.addAttribute("languageBundle", translations); + + if (Utils.isCurrentUserAdmin()) { + return "imprimelibros/pedidos/pedidos-list"; + } + return "imprimelibros/pedidos/pedidos-list-cliente"; + } + + @GetMapping(value = "datatable", produces = "application/json") + @ResponseBody + public DataTablesResponse> getDatatable( + HttpServletRequest request, + Principal principal, + Locale locale) { + + DataTablesRequest dt = DataTablesParser.from(request); + + Boolean isAdmin = Utils.isCurrentUserAdmin(); + Long currentUserId = Utils.currentUserId(principal); + + List searchable = List.of( + "id"); + + // Campos ordenables + List orderable = List.of( + "id", + "createdBy.fullName", + "createdAt", + "total", + "estado"); + + Specification base = (root, query, cb) -> cb.conjunction(); + if (!isAdmin) { + base = base.and((root, query, cb) -> cb.equal(root.get("createdBy").get("id"), currentUserId)); + } + String clientSearch = dt.getColumnSearch("cliente"); + String estadoSearch = dt.getColumnSearch("estado"); + + // 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification + if (clientSearch != null) { + List userIds = repoUser.findIdsByFullNameLike(clientSearch.trim()); + + if (userIds.isEmpty()) { + // Ningún usuario coincide → forzamos 0 resultados + base = base.and((root, query, cb) -> cb.disjunction()); + } else { + base = base.and((root, query, cb) -> root.get("createdBy").in(userIds)); + } + } + if (estadoSearch != null && !estadoSearch.isBlank()) { + try { + PedidoLinea.Estado estadoEnum = PedidoLinea.Estado.valueOf(estadoSearch.trim()); + + base = base.and((root, query, cb) -> { + // Evitar duplicados de pedidos si el provider usa joins + if (Pedido.class.equals(query.getResultType())) { + query.distinct(true); + } + + Join lineas = root.join("lineas", JoinType.INNER); + return cb.equal(lineas.get("estado"), estadoEnum); + }); + + } catch (IllegalArgumentException ex) { + // Valor de estado no válido → forzamos 0 resultados + base = base.and((root, query, cb) -> cb.disjunction()); + } + } + Long total = repoPedido.count(base); + + return DataTable + .of(repoPedido, Pedido.class, dt, searchable) + .orderable(orderable) + .add("id", Pedido::getId) + .add("created_at", pedido -> Utils.formatInstant(pedido.getCreatedAt(), locale)) + .add("cliente", pedido -> { + if (pedido.getCreatedBy() != null) { + return pedido.getCreatedBy().getFullName(); + } + return ""; + }) + .add("total", pedido -> { + if (pedido.getTotal() != null) { + return Utils.formatCurrency(pedido.getTotal(), locale); + } else { + return ""; + } + }) + .add("estado", pedido -> { + List lineas = repoPedidoLinea.findByPedidoId(pedido.getId()); + if (lineas.isEmpty()) { + return ""; + } + // concatenar los estados de las líneas, ordenados por prioridad + StringBuilder sb = new StringBuilder(); + lineas.stream() + .map(PedidoLinea::getEstado) + .distinct() + .sorted(Comparator.comparingInt(PedidoLinea.Estado::getPriority)) + .forEach(estado -> { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(messageSource.getMessage(estado.getMessageKey(), null, locale)); + }); + String text = sb.toString(); + return text; + }) + .add("actions", pedido -> { + String data = "" + + messageSource.getMessage("app.view", null, locale) + ""; + List lineas = repoPedidoLinea.findByPedidoId(pedido.getId()); + boolean hasDenegadoPago = lineas.stream() + .anyMatch(linea -> PedidoLinea.Estado.denegado_pago.equals(linea.getEstado())); + if (hasDenegadoPago) { + data += " " + + messageSource.getMessage("app.pay", null, locale) + ""; + } + return data; + }) + .where(base) + .toJson(total); + + } + + @GetMapping("/view/{id}") + public String verPedido( + @PathVariable(name = "id", required = true) Long id, + Model model, Locale locale) { + + Boolean isAdmin = Utils.isCurrentUserAdmin(); + if (isAdmin) { + model.addAttribute("isAdmin", true); + } else { + model.addAttribute("isAdmin", false); + } + + PedidoDireccion direccionFacturacion = pedidoService.getDireccionFacturacionPedido(id); + if (direccionFacturacion != null) { + String paisNombre = paisesService.getPaisNombrePorCode3(direccionFacturacion.getPaisCode3(), locale); + direccionFacturacion.setPaisNombre(paisNombre); + } + + model.addAttribute("direccionFacturacion", direccionFacturacion); + + List> lineas = pedidoService.getLineas(id, locale); + for (Map linea : lineas) { + + PedidoLinea pedidoLinea = repoPedidoLinea.findById( + ((Number) linea.get("lineaId")).longValue()).orElse(null); + if (pedidoLinea != null) { + Map buttons = new HashMap<>(); + if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.esperando_aceptacion_ferro.getPriority() + && pedidoLinea.getEstado().getPriority() <= PedidoLinea.Estado.produccion.getPriority()) { + + if (pedidoLinea.getEstado() == PedidoLinea.Estado.esperando_aceptacion_ferro) { + buttons.put("aceptar_ferro", true); + } else { + buttons.put("aceptar_ferro", false); + } + + Map filesType = pedidoService.getFilesType(pedidoLinea.getId(), locale); + if (filesType == null || filesType.get("error") != null) { + throw new RuntimeException( + messageSource.getMessage("pedido.errors.update-server-error", null, locale)); + } + for (String key : filesType.keySet()) { + buttons.put(key, (Integer) filesType.get(key) == 1 ? true : false); + } + linea.put("buttons", buttons); + } + } + + List dirEntrega = pedidoService.getDireccionesEntregaPedidoLinea( + ((Number) linea.get("lineaId")).longValue()); + + if (dirEntrega != null && !dirEntrega.isEmpty()) { + for (PedidoDireccion direccion : dirEntrega) { + String paisNombre = paisesService.getPaisNombrePorCode3(direccion.getPaisCode3(), locale); + direccion.setPaisNombre(paisNombre); + } + } + linea.put("direccionesEntrega", dirEntrega); + + } + model.addAttribute("lineas", lineas); + model.addAttribute("id", id); + return "imprimelibros/pedidos/pedidos-view"; + } + + // ------------------------------------- + // Acciones sobre las lineas de pedido + // ------------------------------------- + @PostMapping("/linea/{id}/update-status") + @ResponseBody + public Map updateStatus( + @PathVariable(name = "id", required = true) Long id, Locale locale) { + + Map result = pedidoService.actualizarEstado(id, locale); + + return result; + } + + @PostMapping("/linea/{id}/update-maquetacion") + @ResponseBody + public Map updateMaquetacion( + @PathVariable(name = "id", required = true) Long id, + Locale locale) { + + PedidoLinea entity = repoPedidoLinea.findById(id).orElse(null); + if (entity == null) { + String errorMsg = messageSource.getMessage("pedido.errors.linea-not-found", null, locale); + return Map.of( + "success", false, + "message", errorMsg); + } + + if (entity.getEstado() != PedidoLinea.Estado.maquetacion) { + String errorMsg = messageSource.getMessage("pedido.errors.state-error", null, locale); + return Map.of( + "success", false, + "message", errorMsg); + } + + entity.setEstado(PedidoLinea.Estado.haciendo_ferro); + repoPedidoLinea.save(entity); + String successMsg = messageSource.getMessage("pedido.success.estado-actualizado", null, locale); + return Map.of( + "success", true, + "message", successMsg, + "state", messageSource.getMessage(entity.getEstado().getMessageKey(), null, locale)); + } + + @GetMapping("/linea/{id}/download-ferro") + public ResponseEntity downloadFerro(@PathVariable(name = "id", required = true) Long id, Locale locale) { + + byte[] ferroFileContent = pedidoService.getFerroFileContent(id, locale); + if (ferroFileContent == null) { + return ResponseEntity.notFound().build(); + } + + ByteArrayResource resource = new ByteArrayResource(ferroFileContent); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=ferro_" + id + ".pdf") + .contentType(MediaType.APPLICATION_PDF) + .body(resource); + } + + @GetMapping("/linea/{id}/download-cub") + public ResponseEntity downloadCubierta(@PathVariable(name = "id", required = true) Long id, + Locale locale) { + + byte[] cubFileContent = pedidoService.getCubiertaFileContent(id, locale); + if (cubFileContent == null) { + return ResponseEntity.notFound().build(); + } + + ByteArrayResource resource = new ByteArrayResource(cubFileContent); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=cubierta_" + id + ".pdf") + .contentType(MediaType.APPLICATION_PDF) + .body(resource); + } + + @GetMapping("/linea/{id}/download-tapa") + public ResponseEntity downloadTapa(@PathVariable(name = "id", required = true) Long id, Locale locale) { + + byte[] tapaFileContent = pedidoService.getTapaFileContent(id, locale); + if (tapaFileContent == null) { + return ResponseEntity.notFound().build(); + } + + ByteArrayResource resource = new ByteArrayResource(tapaFileContent); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=tapa_" + id + ".pdf") + .contentType(MediaType.APPLICATION_PDF) + .body(resource); + } + + @PostMapping("/linea/{id}/aceptar-ferro") + @ResponseBody + public Map aceptarFerro(@PathVariable(name = "id", required = true) Long id, + Locale locale) { + + PedidoLinea entity = repoPedidoLinea.findById(id).orElse(null); + if (entity == null) { + String errorMsg = messageSource.getMessage("pedido.errors.linea-not-found", null, locale); + return Map.of( + "success", false, + "message", errorMsg); + } + + if (entity.getEstado() != PedidoLinea.Estado.esperando_aceptacion_ferro) { + String errorMsg = messageSource.getMessage("pedido.errors.state-error", null, locale); + return Map.of( + "success", false, + "message", errorMsg); + } + + Boolean result = pedidoService.aceptarFerro(id, locale); + + if (result) { + String successMsg = messageSource.getMessage("pedido.success.estado-actualizado", null, locale); + return Map.of( + "success", true, + "message", successMsg, + "state", messageSource.getMessage(entity.getEstado().getMessageKey(), null, locale)); + } else { + String errorMsg = messageSource.getMessage("pedido.errors.update-server-error", null, locale); + return Map.of( + "success", false, + "message", errorMsg); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java index 8606ca8..fd06501 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java @@ -147,7 +147,9 @@ public class PresupuestoController { return ResponseEntity.badRequest().body(errores); } Map resultado = new HashMap<>(); - resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale)); + Map datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale); + resultado.put("solapas", datosInterior.get("maxSolapas")); + resultado.put("lomo", datosInterior.get("lomo")); resultado.putAll(presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale)); return ResponseEntity.ok(resultado); } @@ -267,7 +269,10 @@ public class PresupuestoController { } } - resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale)); + Map datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale); + resultado.put("solapas", datosInterior.get("maxSolapas")); + resultado.put("lomo", datosInterior.get("lomo")); + return ResponseEntity.ok(resultado); } @@ -300,7 +305,10 @@ public class PresupuestoController { presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción } } - resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale)); + Map datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale); + resultado.put("solapas", datosInterior.get("maxSolapas")); + resultado.put("lomo", datosInterior.get("lomo")); + return ResponseEntity.ok(resultado); } @@ -323,7 +331,10 @@ public class PresupuestoController { } Map resultado = new HashMap<>(); - resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale)); + Map datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale); + resultado.put("solapas", datosInterior.get("maxSolapas")); + resultado.put("lomo", datosInterior.get("lomo")); + return ResponseEntity.ok(resultado); } @@ -492,7 +503,8 @@ public class PresupuestoController { String sessionId = request.getSession(true).getId(); String ip = IpUtils.getClientIp(request); - var resumen = presupuestoService.getResumen(p, serviciosList, datosMaquetacion, datosMarcapaginas, save, mode, locale, sessionId, ip); + var resumen = presupuestoService.getResumen(p, serviciosList, datosMaquetacion, datosMarcapaginas, save, mode, + locale, sessionId, ip); return ResponseEntity.ok(resumen); } @@ -519,7 +531,27 @@ public class PresupuestoController { "presupuesto.add.cancel", "presupuesto.add.select-client", "presupuesto.add.error.options", - "presupuesto.add.error.options-client"); + "presupuesto.add.error.options-client", + "presupuesto.duplicar.title", + "presupuesto.duplicar.text", + "presupuesto.duplicar.confirm", + "presupuesto.duplicar.cancelar", + "presupuesto.duplicar.aceptar", + "presupuesto.duplicar.required", + "presupuesto.duplicar.success.title", + "presupuesto.duplicar.success.text", + "presupuesto.duplicar.error.title", + "presupuesto.duplicar.error.internal", + "presupuesto.reimprimir.title", + "presupuesto.reimprimir.text", + "presupuesto.reimprimir.confirm", + "presupuesto.reimprimir.cancelar", + "presupuesto.reimprimir.aceptar", + "presupuesto.reimprimir.success.title", + "presupuesto.reimprimir.success.text", + "presupuesto.reimprimir.error.title", + "presupuesto.reimprimir.error.internal" + ); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); @@ -543,7 +575,26 @@ public class PresupuestoController { "presupuesto.exito.guardado", "presupuesto.add.error.save.title", "presupuesto.iva-reducido", - "presupuesto.iva-reducido-descripcion"); + "presupuesto.iva-reducido-descripcion", + "presupuesto.duplicar.title", + "presupuesto.duplicar.text", + "presupuesto.duplicar.confirm", + "presupuesto.duplicar.cancelar", + "presupuesto.duplicar.aceptar", + "presupuesto.duplicar.required", + "presupuesto.duplicar.success.title", + "presupuesto.duplicar.success.text", + "presupuesto.duplicar.error.title", + "presupuesto.duplicar.error.internal", + "presupuesto.reimprimir.title", + "presupuesto.reimprimir.text", + "presupuesto.reimprimir.confirm", + "presupuesto.reimprimir.cancelar", + "presupuesto.reimprimir.aceptar", + "presupuesto.reimprimir.success.title", + "presupuesto.reimprimir.success.text", + "presupuesto.reimprimir.error.title", + "presupuesto.reimprimir.error.internal"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); @@ -562,15 +613,15 @@ public class PresupuestoController { return "redirect:/presupuesto"; } - if(presupuestoOpt.get().getEstado() == Presupuesto.Estado.aceptado){ + if (presupuestoOpt.get().getEstado() == Presupuesto.Estado.aceptado) { Map resumen = presupuestoService.getTextosResumen( - presupuestoOpt.get(), - Utils.decodeJsonList(presupuestoOpt.get().getServiciosJson()), - Utils.decodeJsonMap(presupuestoOpt.get().getDatosMaquetacionJson()), - Utils.decodeJsonMap(presupuestoOpt.get().getDatosMarcapaginasJson()), - locale); - + presupuestoOpt.get(), + Utils.decodeJsonList(presupuestoOpt.get().getServiciosJson()), + Utils.decodeJsonMap(presupuestoOpt.get().getDatosMaquetacionJson()), + Utils.decodeJsonMap(presupuestoOpt.get().getDatosMarcapaginasJson()), + locale); + model.addAttribute("resumen", resumen); model.addAttribute("presupuesto", presupuestoOpt.get()); return "imprimelibros/presupuestos/presupuestador-view"; @@ -595,6 +646,7 @@ public class PresupuestoController { model.addAttribute("appMode", "edit"); } model.addAttribute("id", presupuestoOpt.get().getId()); + model.addAttribute("presupuesto", presupuestoOpt.get()); return "imprimelibros/presupuestos/presupuesto-form"; } @@ -780,4 +832,30 @@ public class PresupuestoController { } } + @PostMapping("/{id}/comentario") + @ResponseBody + public String actualizarComentario(@PathVariable Long id, + @RequestParam String comentario) { + presupuestoService.updateComentario(id, comentario); + return "OK"; + } + + @PostMapping("/api/duplicar/{id}") + @ResponseBody + public Map duplicarPresupuesto( + @PathVariable Long id, + @RequestParam(name = "titulo", defaultValue = "") String titulo) { + + Long entity = presupuestoService.duplicarPresupuesto(id, titulo); + return Map.of("id", entity); + } + + + @PostMapping("/api/reimprimir/{id}") + @ResponseBody + public Map reimprimirPresupuesto(@PathVariable Long id) { + + Long entity = presupuestoService.reimprimirPresupuesto(id); + return Map.of("id", entity); + } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java index 4d0bd5d..089f2ae 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java @@ -86,7 +86,7 @@ public class PresupuestoDatatableService { .addIf(publico, "ciudad", Presupuesto::getCiudad) .add("updatedAt", p -> formatDate(p.getUpdatedAt(), locale)) .addIf(!publico, "user", p -> p.getUser() != null ? p.getUser().getFullName() : "") - .add("actions", this::generarBotones) + .add("actions", p -> generarBotones(p, locale)) .where(base) .toJson(count); } @@ -115,18 +115,27 @@ public class PresupuestoDatatableService { return df.format(instant); } - private String generarBotones(Presupuesto p) { + private String generarBotones(Presupuesto p, Locale locale) { boolean borrador = p.getEstado() == Presupuesto.Estado.borrador; String id = String.valueOf(p.getId()); String editBtn = ""; + (p.getOrigen().equals(Presupuesto.Origen.publico) || p.getEstado() == Presupuesto.Estado.aceptado ? "eye" : "pencil") + "-line\" " + + "data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"" + + msg(p.getEstado() == Presupuesto.Estado.aceptado ? "presupuesto.ver" : "presupuesto.editar", locale) + "\">"; + String duplicarBtn = !p.getOrigen().equals(Presupuesto.Origen.publico) ? "" : ""; + String reimprimirBtn = p.getEstado() == Presupuesto.Estado.aceptado && !p.getOrigen().equals(Presupuesto.Origen.publico) ? "" : ""; String deleteBtn = borrador ? "" : ""; + + " fs-15\">" : ""; - return "
" + editBtn + deleteBtn + "
"; + return "
" + editBtn + duplicarBtn + reimprimirBtn + deleteBtn + "
"; } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java index 534b1d1..26342b1 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java @@ -25,6 +25,7 @@ public class PresupuestoPapeles { ); private static final Map CABEZADA_COLOR_KEYS = Map.of( + "NOCABE", "presupuesto.cabezada-sin-cabezada", "WHI", "presupuesto.cabezada-blanca", "GRE", "presupuesto.cabezada-verde", "BLUE", "presupuesto.cabezada-azul", diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java b/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java index ea5bebe..132832c 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/dto/Presupuesto.java @@ -387,6 +387,9 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable { @Column(name = "proveedor_ref2") private Long proveedorRef2; + @Column(name = "is_reimpresion", nullable = false) + private Boolean isReimpresion = false; + // ====== MÉTODOS AUX ====== public String resumenPresupuesto() { @@ -965,6 +968,14 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable { this.proveedorRef2 = proveedorRef2; } + public Boolean getIsReimpresion() { + return isReimpresion; + } + + public void setIsReimpresion(Boolean isReimpresion) { + this.isReimpresion = isReimpresion; + } + public void setId(Long id) { this.id = id; } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java index b5af207..0afc5ee 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.math.BigDecimal; import java.math.RoundingMode; +import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.web.IpUtils; import com.imprimelibros.erp.configurationERP.VariableService; import com.imprimelibros.erp.presupuesto.GeoIpService; @@ -71,14 +72,16 @@ public class PresupuestoService { private final skApiClient apiClient; private final GeoIpService geoIpService; private final UserDao userRepo; + private final Utils utils; public PresupuestoService(PresupuestadorItems presupuestadorItems, PresupuestoFormatter presupuestoFormatter, - skApiClient apiClient, GeoIpService geoIpService, UserDao userRepo) { + skApiClient apiClient, GeoIpService geoIpService, UserDao userRepo, Utils utils) { this.presupuestadorItems = presupuestadorItems; this.presupuestoFormatter = presupuestoFormatter; this.apiClient = apiClient; this.geoIpService = geoIpService; this.userRepo = userRepo; + this.utils = utils; } public boolean validateDatosGenerales(int[] tiradas) { @@ -348,6 +351,11 @@ public class PresupuestoService { body.put("interior", interior); body.put("cubierta", cubierta); body.put("guardas", null); + // Para las reimpresiones + if(presupuesto.getIsReimpresion() != null && presupuesto.getIsReimpresion()) { + body.put("reimpresion", 1); + body.put("iskn", presupuesto.getProveedorRef1()); + } if (presupuesto.getSobrecubierta()) { Map sobrecubierta = new HashMap<>(); sobrecubierta.put("papel", presupuesto.getPapelSobrecubiertaId()); @@ -1020,6 +1028,7 @@ public class PresupuestoService { resumen.put("iva_importe_4", presupuesto.getIvaImporte4()); resumen.put("iva_importe_21", presupuesto.getIvaImporte21()); resumen.put("total_con_iva", presupuesto.getTotalConIva()); + resumen.put("isReimpresion", presupuesto.getIsReimpresion()); return resumen; } @@ -1143,6 +1152,7 @@ public class PresupuestoService { if(presupuesto.getSelectedTirada() != null && presupuesto.getSelectedTirada().equals(tirada)) presupuesto.setServiciosJson(new ObjectMapper().writeValueAsString(servicios)); } catch (Exception ignore) { + System.out.println("Error guardando servicios JSON: " + ignore.getMessage()); } } @@ -1222,6 +1232,18 @@ public class PresupuestoService { HashMap result = new HashMap<>(); try { + Presupuesto presupuestoExistente = null; + if (id != null) { + presupuestoExistente = presupuestoRepository.findById(id).orElse(null); + } + if (presupuestoExistente != null) { + // merge de datos que no están en el formulario + presupuesto.setIsReimpresion(presupuestoExistente.getIsReimpresion()); + presupuesto.setProveedor(presupuestoExistente.getProveedor()); + presupuesto.setProveedorRef1(presupuestoExistente.getProveedorRef1()); + presupuesto.setProveedorRef2(presupuestoExistente.getProveedorRef2()); + } + presupuesto.setDatosMaquetacionJson( datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null); presupuesto.setDatosMarcapaginasJson( @@ -1310,6 +1332,93 @@ public class PresupuestoService { } return true; } + + public Boolean hasMaquetacion(Presupuesto presupuesto) { + if (presupuesto.getServiciosJson() != null && !presupuesto.getServiciosJson().isEmpty()) { + if(presupuesto.getServiciosJson().contains("maquetacion")) { + return true; + } + } + return false; + } + + public Map getPresupuestoInfoForCard(Presupuesto presupuesto, Locale locale) { + + Map resumen = new HashMap<>(); + + resumen.put("titulo", presupuesto.getTitulo()); + + resumen.put("imagen", + "/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion() + ".png"); + resumen.put("imagen_alt", + messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null, locale)); + + resumen.put("presupuestoId", presupuesto.getId()); + + if (presupuesto.getServiciosJson() != null && presupuesto.getServiciosJson().contains("ejemplar-prueba")) { + resumen.put("hasSample", true); + } else { + resumen.put("hasSample", false); + } + Map detalles = utils.getTextoPresupuesto(presupuesto, locale); + + resumen.put("tirada", presupuesto.getSelectedTirada()); + + resumen.put("baseTotal", Utils.formatCurrency(presupuesto.getBaseImponible(), locale)); + resumen.put("base", presupuesto.getBaseImponible()); + resumen.put("iva4", presupuesto.getIvaImporte4()); + resumen.put("iva21", presupuesto.getIvaImporte21()); + resumen.put("total", Utils.formatCurrency(presupuesto.getTotalConIva(), locale)); + + resumen.put("resumen", detalles); + + return resumen; + } + + public Presupuesto findPresupuestoById(Long id) { + return presupuestoRepository.findById(id).orElse(null); + } + + public void updateComentario(Long presupuestoId, String comentario) { + Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId).orElse(null); + if (presupuesto != null) { + presupuesto.setComentario(comentario); + presupuestoRepository.saveAndFlush(presupuesto); + } + } + + public long duplicarPresupuesto(Long presupuestoId, String titulo) { + + Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId).orElse(null); + if (presupuesto != null) { + Presupuesto nuevo = presupuesto.clone(); + nuevo.setId(null); // para que se genere uno nuevo + nuevo.setEstado(Presupuesto.Estado.borrador); + nuevo.setTitulo(titulo != null && !titulo.isEmpty() ? titulo : "[D] " + presupuesto.getTitulo()); + nuevo.setIsReimpresion(false); + nuevo.setProveedor(null); + nuevo.setProveedorRef1(null); + nuevo.setProveedorRef2(null); + presupuestoRepository.saveAndFlush(nuevo); + return nuevo.getId(); + } + return -1; + } + + public long reimprimirPresupuesto(Long presupuestoId) { + + Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId).orElse(null); + if (presupuesto != null) { + Presupuesto nuevo = presupuesto.clone(); + nuevo.setId(null); // para que se genere uno nuevo + nuevo.setEstado(Presupuesto.Estado.borrador); + nuevo.setTitulo("[R] " + presupuesto.getTitulo()); + nuevo.setIsReimpresion(true); + presupuestoRepository.saveAndFlush(nuevo); + return nuevo.getId(); + } + return -1; + } // ======================================================================= // Métodos privados diff --git a/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java index 537905d..68a9f4a 100644 --- a/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java +++ b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java @@ -1,10 +1,15 @@ package com.imprimelibros.erp.redsys; +import com.imprimelibros.erp.cart.Cart; import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.payments.PaymentService; import com.imprimelibros.erp.payments.model.Payment; +import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository; +import com.imprimelibros.erp.pedidos.Pedido; +import com.imprimelibros.erp.pedidos.PedidoService; import com.imprimelibros.erp.redsys.RedsysService.FormPayload; +import groovy.util.logging.Log; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -27,6 +32,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.nio.charset.StandardCharsets; import java.util.Locale; +import java.util.Map; import java.util.UUID; @Controller @@ -37,26 +43,37 @@ public class RedsysController { private final MessageSource messageSource; private final SpringTemplateEngine templateEngine; private final ServletContext servletContext; + private final PedidoService pedidoService; public RedsysController(PaymentService paymentService, MessageSource messageSource, - SpringTemplateEngine templateEngine, ServletContext servletContext) { + SpringTemplateEngine templateEngine, ServletContext servletContext, + PedidoService pedidoService) { this.paymentService = paymentService; this.messageSource = messageSource; this.templateEngine = templateEngine; this.servletContext = servletContext; + this.pedidoService = pedidoService; } @PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @ResponseBody public ResponseEntity crearPago(@RequestParam("amountCents") Long amountCents, @RequestParam("method") String method, @RequestParam("cartId") Long cartId, + @RequestParam(value = "dirFactId", required = false) Long dirFactId, HttpServletRequest request, HttpServletResponse response, Locale locale) throws Exception { + // Creamos el pedido inteno + Pedido order = pedidoService.crearPedido(cartId, dirFactId, null, null); + if ("bank-transfer".equalsIgnoreCase(method)) { + // 1) Creamos el Payment interno SIN orderId (null) - Payment p = paymentService.createBankTransferPayment(cartId, amountCents, "EUR"); + Payment p = paymentService.createBankTransferPayment(cartId, dirFactId, amountCents, "EUR", locale, + order.getId()); + + pedidoService.markPedidoAsProcesingPayment(order.getId()); // 1️⃣ Crear la "aplicación" web de Thymeleaf (Jakarta) JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext); @@ -88,7 +105,104 @@ public class RedsysController { } // Tarjeta o Bizum (Redsys) - FormPayload form = paymentService.createRedsysPayment(cartId, amountCents, "EUR", method); + FormPayload form = paymentService.createRedsysPayment(cartId, dirFactId, amountCents, "EUR", method, + order.getId()); + + String html = """ + Redirigiendo a Redsys… + +
+ + + + + +
+ + """.formatted( + form.action(), + form.signatureVersion(), + form.merchantParameters(), + form.signature(), cartId); + + byte[] body = html.getBytes(StandardCharsets.UTF_8); + return ResponseEntity.ok() + .contentType(MediaType.TEXT_HTML) + .body(body); + } + + @PostMapping(value = "/reintentar", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + @ResponseBody + public ResponseEntity reintentarPago(@RequestParam("amountCents") Long amountCents, + @RequestParam("method") String method, @RequestParam("orderId") Long orderId, + HttpServletRequest request, + HttpServletResponse response, Locale locale) + throws Exception { + + // Creamos el pedido inteno + Pedido order = pedidoService.findById(orderId); + + // Find the payment with orderId = order.getId() and status = failed + Payment failedPayment = paymentService.findFailedPaymentByOrderId(order.getId()); + if (failedPayment == null) { + throw new Exception("No se encontró un pago fallido para el pedido " + order.getId()); + } + + Long cartId = null; + Long dirFactId = null; + // Find payment transaction details from failedPayment if needed + try { + Map transactionDetails = paymentService.getPaymentTransactionData(failedPayment.getId()); + cartId = transactionDetails.get("cartId"); + dirFactId = transactionDetails.get("dirFactId"); + } catch (Exception e) { + throw new Exception( + "No se pudieron obtener los detalles de la transacción para el pago " + failedPayment.getId()); + } + + if ("bank-transfer".equalsIgnoreCase(method)) { + + // 1) Creamos el Payment interno SIN orderId (null) + Payment p = paymentService.createBankTransferPayment(cartId, dirFactId, amountCents, "EUR", locale, + order.getId()); + + pedidoService.markPedidoAsProcesingPayment(order.getId()); + + // 1️⃣ Crear la "aplicación" web de Thymeleaf (Jakarta) + JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext); + + // 2️⃣ Construir el intercambio web desde request/response + response.setContentType("text/html;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + IWebExchange exchange = app.buildExchange(request, response); + + // 3️⃣ Crear el contexto WebContext con Locale + WebContext ctx = new WebContext(exchange, locale); + + String importeFormateado = Utils.formatCurrency(amountCents / 100.0, locale); + ctx.setVariable("importe", importeFormateado); + ctx.setVariable("concepto", "TRANSF-" + p.getOrderId()); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + boolean isAuth = auth != null + && auth.isAuthenticated() + && !(auth instanceof AnonymousAuthenticationToken); + ctx.setVariable("isAuth", isAuth); + + // 3) Renderizamos la plantilla a HTML + String html = templateEngine.process("imprimelibros/pagos/transfer", ctx); + + byte[] body = html.getBytes(StandardCharsets.UTF_8); + return ResponseEntity.ok() + .contentType(MediaType.TEXT_HTML) + .body(body); + } + + // Tarjeta o Bizum (Redsys) + FormPayload form = paymentService.createRedsysPayment(cartId, dirFactId, amountCents, "EUR", method, + order.getId()); String html = """ Redirigiendo a Redsys… diff --git a/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java b/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java index 1d1a85f..8775a2e 100644 --- a/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java +++ b/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java @@ -49,7 +49,7 @@ public class RedsysService { // ---------- RECORDS ---------- // Pedido a Redsys - public record PaymentRequest(String order, long amountCents, String description, Long cartId) { + public record PaymentRequest(String order, long amountCents, String description, Long cartId, Long dirFactId) { } // Payload para el formulario @@ -84,7 +84,10 @@ public class RedsysService { // Si tu PaymentRequest no lo lleva todavía, puedes pasarlo en description o // crear otro campo. JSONObject ctx = new JSONObject(); - ctx.put("cartId", req.cartId()); // o req.cartId() si decides añadirlo al record + ctx.put("cartId", req.cartId()); + if (req.dirFactId() != null) { + ctx.put("dirFactId", req.dirFactId()); + } api.setParameter("DS_MERCHANT_MERCHANTDATA", ctx.toString()); if (req.description() != null && !req.description().isBlank()) { @@ -195,6 +198,7 @@ public class RedsysService { public final long amountCents; public final String currency; public final Long cartId; + public final Long dirFactId; public final String processedPayMethod; // Ds_ProcessedPayMethod public final String bizumIdOper; // Ds_Bizum_IdOper public final String authorisationCode; // Ds_AuthorisationCode @@ -206,6 +210,7 @@ public class RedsysService { this.currency = str(raw.get("Ds_Currency")); this.amountCents = parseLongSafe(raw.get("Ds_Amount")); this.cartId = extractCartId(raw.get("Ds_MerchantData")); + this.dirFactId = extractDirFactId(raw.get("Ds_MerchantData")); this.processedPayMethod = str(raw.get("Ds_ProcessedPayMethod")); this.bizumIdOper = str(raw.get("Ds_Bizum_IdOper")); this.authorisationCode = str(raw.get("Ds_AuthorisationCode")); @@ -228,6 +233,24 @@ public class RedsysService { } } + private static Long extractDirFactId(Object merchantDataObj) { + if (merchantDataObj == null) + return null; + try { + String json = String.valueOf(merchantDataObj); + + // 👇 DES-ESCAPAR las comillas HTML que vienen de Redsys + json = json.replace(""", "\""); + + org.json.JSONObject ctx = new org.json.JSONObject(json); + long v = ctx.optLong("dirFactId", 0L); + return v != 0L ? v : null; + } catch (Exception e) { + e.printStackTrace(); // te ayudará si vuelve a fallar + return null; + } + } + public boolean authorized() { try { int r = Integer.parseInt(response); diff --git a/src/main/java/com/imprimelibros/erp/users/UserService.java b/src/main/java/com/imprimelibros/erp/users/UserService.java index fecd512..688aeb8 100644 --- a/src/main/java/com/imprimelibros/erp/users/UserService.java +++ b/src/main/java/com/imprimelibros/erp/users/UserService.java @@ -17,4 +17,5 @@ public interface UserService extends UserDetailsService { * @return página de usuarios */ Page findByRoleAndSearch(String role, String query, Pageable pageable); + User findById(Long id); } diff --git a/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java b/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java index 10370f2..e187e7b 100644 --- a/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java +++ b/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java @@ -31,4 +31,8 @@ public class UserServiceImpl implements UserService { if (query == null || query.isBlank()) query = null; return userDao.searchUsers(role, query, pageable); } + + public User findById(Long id) { + return userDao.findById(id).orElse(null); + } } diff --git a/src/main/resources/db/changelog/changesets/0014-create-pedidos-direcciones.yml b/src/main/resources/db/changelog/changesets/0014-create-pedidos-direcciones.yml new file mode 100644 index 0000000..7129fd3 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0014-create-pedidos-direcciones.yml @@ -0,0 +1,151 @@ +databaseChangeLog: + - changeSet: + id: 0014-create-pedidos-direcciones + author: jjo + changes: + - createTable: + tableName: pedidos_direcciones + columns: + - column: + name: id + type: BIGINT AUTO_INCREMENT + constraints: + primaryKey: true + nullable: false + + - column: + name: pedido_linea_id + type: BIGINT + constraints: + nullable: true + + - column: + name: pedido_id + type: BIGINT + constraints: + nullable: true + + - column: + name: unidades + type: MEDIUMINT UNSIGNED + constraints: + nullable: true + + - column: + name: is_facturacion + type: TINYINT(1) + defaultValueNumeric: 0 + constraints: + nullable: false + + - column: + name: is_ejemplar_prueba + type: TINYINT(1) + defaultValueNumeric: 0 + constraints: + nullable: false + + - column: + name: att + type: VARCHAR(150) + constraints: + nullable: false + + - column: + name: direccion + type: VARCHAR(255) + constraints: + nullable: false + + - column: + name: cp + type: MEDIUMINT UNSIGNED + constraints: + nullable: false + + - column: + name: ciudad + type: VARCHAR(100) + constraints: + nullable: false + + - column: + name: provincia + type: VARCHAR(100) + constraints: + nullable: false + + - column: + name: pais_code3 + type: CHAR(3) + defaultValue: esp + constraints: + nullable: false + + - column: + name: telefono + type: VARCHAR(30) + constraints: + nullable: false + + - column: + name: instrucciones + type: VARCHAR(255) + constraints: + nullable: true + + - column: + name: razon_social + type: VARCHAR(150) + constraints: + nullable: true + + - column: + name: tipo_identificacion_fiscal + type: ENUM('DNI','NIE','CIF','Pasaporte','VAT_ID') + defaultValue: DNI + constraints: + nullable: false + + - column: + name: identificacion_fiscal + type: VARCHAR(50) + constraints: + nullable: true + + - column: + name: created_at + type: TIMESTAMP + defaultValueComputed: CURRENT_TIMESTAMP + constraints: + nullable: false + + - addForeignKeyConstraint: + baseTableName: pedidos_direcciones + baseColumnNames: pedido_linea_id + referencedTableName: pedidos_lineas + referencedColumnNames: id + constraintName: fk_pedidos_direcciones_pedido_linea + onDelete: SET NULL + onUpdate: CASCADE + + - addForeignKeyConstraint: + baseTableName: pedidos_direcciones + baseColumnNames: pedido_id + referencedTableName: pedidos + referencedColumnNames: id + constraintName: fk_pedidos_direcciones_pedidos + onDelete: SET NULL + onUpdate: CASCADE + + - createIndex: + tableName: pedidos_direcciones + indexName: idx_pedidos_direcciones_pedido_linea_id + columns: + - column: + name: pedido_linea_id + + rollback: + - dropTable: + tableName: pedidos_direcciones + cascadeConstraints: true diff --git a/src/main/resources/db/changelog/changesets/0015-alter-pedidos-lineas-and-presupuesto-estados.yml b/src/main/resources/db/changelog/changesets/0015-alter-pedidos-lineas-and-presupuesto-estados.yml new file mode 100644 index 0000000..5fe34ec --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0015-alter-pedidos-lineas-and-presupuesto-estados.yml @@ -0,0 +1,48 @@ +databaseChangeLog: + - changeSet: + id: 0015-alter-pedidos-lineas-and-presupuesto-estados + author: jjo + changes: + # Añadir columnas a pedidos_lineas + - addColumn: + tableName: pedidos_lineas + columns: + - column: + name: estado + type: "ENUM('aprobado','maquetación','haciendo_ferro','producción','terminado','cancelado')" + defaultValue: aprobado + constraints: + nullable: false + afterColumn: presupuesto_id + + - column: + name: estado_manual + type: TINYINT(1) + defaultValueNumeric: 0 + constraints: + nullable: false + afterColumn: estado + + # Añadir columna a presupuesto + - addColumn: + tableName: presupuesto + columns: + - column: + name: is_reimpresion + type: TINYINT(1) + defaultValueNumeric: 0 + constraints: + nullable: false + + rollback: + - dropColumn: + tableName: pedidos_lineas + columnName: estado + + - dropColumn: + tableName: pedidos_lineas + columnName: estado_manual + + - dropColumn: + tableName: presupuesto + columnName: is_reimpresion diff --git a/src/main/resources/db/changelog/changesets/0016-fix-enum-estado-pedidos-lineas.yml b/src/main/resources/db/changelog/changesets/0016-fix-enum-estado-pedidos-lineas.yml new file mode 100644 index 0000000..92ae6c3 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0016-fix-enum-estado-pedidos-lineas.yml @@ -0,0 +1,37 @@ +databaseChangeLog: + - changeSet: + id: 0016-fix-enum-estado-pedidos-lineas + author: jjo + changes: + + # 1) Convertir valores existentes "maquetación" → "maquetacion" + - update: + tableName: pedidos_lineas + columns: + - column: + name: estado + value: "maquetacion" + where: "estado = 'maquetación'" + + # 2) Cambiar ENUM quitando tilde + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: "ENUM('aprobado','maquetacion','haciendo_ferro','producción','terminado','cancelado')" + + rollback: + + # 1) Volver a convertir "maquetacion" → "maquetación" + - update: + tableName: pedidos_lineas + columns: + - column: + name: estado + value: "maquetación" + where: "estado = 'maquetacion'" + + # 2) Restaurar ENUM original + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: "ENUM('aprobado','maquetación','haciendo_ferro','producción','terminado','cancelado')" diff --git a/src/main/resources/db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml b/src/main/resources/db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml new file mode 100644 index 0000000..0e4a3b3 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml @@ -0,0 +1,19 @@ +databaseChangeLog: + - changeSet: + id: add-fecha-entrega-to-pedidos-lineas + author: jjo + changes: + - addColumn: + tableName: pedidos_lineas + columns: + - column: + name: fecha_entrega + type: datetime + constraints: + nullable: true + afterColumn: estado_manual + + rollback: + - dropColumn: + tableName: pedidos_lineas + columnName: fecha_entrega diff --git a/src/main/resources/db/changelog/changesets/0018-change-presupuesto-ch-3.yml b/src/main/resources/db/changelog/changesets/0018-change-presupuesto-ch-3.yml new file mode 100644 index 0000000..3e51bd4 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0018-change-presupuesto-ch-3.yml @@ -0,0 +1,24 @@ +databaseChangeLog: + - changeSet: + id: 0018-change-presupuesto-ch-3 + author: jjo + preConditions: + - dbms: + type: mysql + + changes: + - sql: + splitStatements: false + stripComments: true + sql: | + ALTER TABLE presupuesto + DROP CHECK presupuesto_chk_3; + + rollback: + - sql: + splitStatements: false + stripComments: true + sql: | + ALTER TABLE presupuesto + ADD CONSTRAINT presupuesto_chk_3 + CHECK (tipo_cubierta BETWEEN 0 AND 2); diff --git a/src/main/resources/db/changelog/changesets/0019-add-estados-pago-to-pedidos-lineas.yml b/src/main/resources/db/changelog/changesets/0019-add-estados-pago-to-pedidos-lineas.yml new file mode 100644 index 0000000..04ae683 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0019-add-estados-pago-to-pedidos-lineas.yml @@ -0,0 +1,32 @@ +databaseChangeLog: + - changeSet: + id: 0019-add-estados-pago-to-pedidos-lineas + author: jjo + changes: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'pendiente_pago', + 'procesando_pago', + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) + rollback: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) diff --git a/src/main/resources/db/changelog/changesets/0020-add-estados-pago-to-pedidos-lineas-2.yml b/src/main/resources/db/changelog/changesets/0020-add-estados-pago-to-pedidos-lineas-2.yml new file mode 100644 index 0000000..3246c20 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0020-add-estados-pago-to-pedidos-lineas-2.yml @@ -0,0 +1,35 @@ +databaseChangeLog: + - changeSet: + id: 0020-add-estados-pago-to-pedidos-lineas-2 + author: jjo + changes: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'pendiente_pago', + 'procesando_pago', + 'denegado_pago', + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) + rollback: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'pendiente_pago', + 'procesando_pago', + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) diff --git a/src/main/resources/db/changelog/changesets/0021-add-email-and-is-palets-to-pedidos-direcciones.yml b/src/main/resources/db/changelog/changesets/0021-add-email-and-is-palets-to-pedidos-direcciones.yml new file mode 100644 index 0000000..d612047 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0021-add-email-and-is-palets-to-pedidos-direcciones.yml @@ -0,0 +1,23 @@ +databaseChangeLog: + - changeSet: + id: 0021-add-email-and-is-palets-to-pedidos-direcciones + author: jjo + changes: + - sql: + dbms: mysql + splitStatements: false + stripComments: true + sql: > + ALTER TABLE pedidos_direcciones + ADD COLUMN is_palets TINYINT(1) NOT NULL DEFAULT 0 AFTER identificacion_fiscal, + ADD COLUMN email VARCHAR(255) NULL AFTER is_ejemplar_prueba; + + rollback: + - sql: + dbms: mysql + splitStatements: false + stripComments: true + sql: > + ALTER TABLE pedidos_direcciones + DROP COLUMN is_palets, + DROP COLUMN email; diff --git a/src/main/resources/db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml b/src/main/resources/db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml new file mode 100644 index 0000000..a8cc671 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml @@ -0,0 +1,37 @@ +databaseChangeLog: + - changeSet: + id: 0022-add-estados-pago-to-pedidos-lineas-3 + author: jjo + changes: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'pendiente_pago', + 'procesando_pago', + 'denegado_pago', + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'esperando_aceptacion_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) + rollback: + - modifyDataType: + tableName: pedidos_lineas + columnName: estado + newDataType: > + enum( + 'pendiente_pago', + 'procesando_pago', + 'denegado_pago', + 'aprobado', + 'maquetacion', + 'haciendo_ferro', + 'produccion', + 'terminado', + 'cancelado' + ) diff --git a/src/main/resources/db/changelog/master.yml b/src/main/resources/db/changelog/master.yml index 20f4886..8b470ee 100644 --- a/src/main/resources/db/changelog/master.yml +++ b/src/main/resources/db/changelog/master.yml @@ -24,4 +24,22 @@ databaseChangeLog: - include: file: db/changelog/changesets/0012--drop-unique-gateway-txid-2.yml - include: - file: db/changelog/changesets/0013-drop-unique-refund-gateway-id.yml \ No newline at end of file + file: db/changelog/changesets/0013-drop-unique-refund-gateway-id.yml + - include: + file: db/changelog/changesets/0014-create-pedidos-direcciones.yml + - include: + file: db/changelog/changesets/0015-alter-pedidos-lineas-and-presupuesto-estados.yml + - include: + file: db/changelog/changesets/0016-fix-enum-estado-pedidos-lineas.yml + - include: + file: db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml + - include: + file: db/changelog/changesets/0018-change-presupuesto-ch-3.yml + - include: + file: db/changelog/changesets/0019-add-estados-pago-to-pedidos-lineas.yml + - include: + file: db/changelog/changesets/0020-add-estados-pago-to-pedidos-lineas-2.yml + - include: + file: db/changelog/changesets/0021-add-email-and-is-palets-to-pedidos-direcciones.yml + - include: + file: db/changelog/changesets/0022-add-estados-pago-to-pedidos-lineas-3.yml \ No newline at end of file diff --git a/src/main/resources/i18n/app_es.properties b/src/main/resources/i18n/app_es.properties index 0c5ad19..f4f77df 100644 --- a/src/main/resources/i18n/app_es.properties +++ b/src/main/resources/i18n/app_es.properties @@ -10,6 +10,8 @@ app.add=Añadir app.back=Volver app.eliminar=Eliminar app.imprimir=Imprimir +app.view=Ver +app.pay=Pagar app.acciones.siguiente=Siguiente app.acciones.anterior=Anterior @@ -20,6 +22,7 @@ app.logout=Cerrar sesión app.sidebar.inicio=Inicio app.sidebar.presupuestos=Presupuestos +app.sidebar.pedidos=Pedidos app.sidebar.configuracion=Configuración app.sidebar.usuarios=Usuarios app.sidebar.direcciones=Mis Direcciones diff --git a/src/main/resources/i18n/direcciones_es.properties b/src/main/resources/i18n/direcciones_es.properties index 6e3ee47..4c4fae3 100644 --- a/src/main/resources/i18n/direcciones_es.properties +++ b/src/main/resources/i18n/direcciones_es.properties @@ -35,6 +35,8 @@ direcciones.pasaporte=Pasaporte direcciones.cif=C.I.F. direcciones.vat_id=VAT ID +direcciones.direccionFacturacion=Dirección de facturación + direcciones.delete.title=Eliminar dirección direcciones.delete.button=Si, ELIMINAR direcciones.delete.text=¿Está seguro de que desea eliminar esta dirección?
Esta acción no se puede deshacer. diff --git a/src/main/resources/i18n/pdf_es.properties b/src/main/resources/i18n/pdf_es.properties index d1e2ffc..f41f3e3 100644 --- a/src/main/resources/i18n/pdf_es.properties +++ b/src/main/resources/i18n/pdf_es.properties @@ -27,6 +27,7 @@ pdf.datos-maquetacion=Datos de maquetación: pdf.datos-marcapaginas=Datos de marcapáginas: pdf.incluye-envio=El presupuesto incluye el envío a una dirección de la península. +pdf.presupuesto-validez=Validez del presupuesto: 30 días desde la fecha de emisión. pdf.politica-privacidad=Política de privacidad pdf.politica-privacidad.responsable=Responsable: Impresión Imprime Libros - CIF: B04998886 - Teléfono de contacto: 910052574 diff --git a/src/main/resources/i18n/pedidos_es.properties b/src/main/resources/i18n/pedidos_es.properties index 50b8b27..558c7af 100644 --- a/src/main/resources/i18n/pedidos_es.properties +++ b/src/main/resources/i18n/pedidos_es.properties @@ -13,6 +13,54 @@ checkout.payment.bizum=Bizum checkout.payment.bank-transfer=Transferencia bancaria checkout.error.payment=Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo. checkout.success.payment=Pago realizado con éxito. Gracias por su compra. +checkout.error.select-method=Por favor, seleccione un método de pago. checkout.make-payment=Realizar el pago -checkout.authorization-required=Certifico que tengo los derechos para imprimir los archivos incluidos en mi pedido y me hago responsable en caso de reclamación de los mismos \ No newline at end of file +checkout.authorization-required=Certifico que tengo los derechos para imprimir los archivos incluidos en mi pedido y me hago responsable en caso de reclamación de los mismos + +pedido.estado.pendiente_pago=Pendiente de pago +pedido.estado.procesando_pago=Procesando pago +pedido.estado.denegado_pago=Pago denegado +pedido.estado.aprobado=Aprobado +pedido.estado.maquetacion=Maquetación +pedido.estado.haciendo_ferro=Haciendo ferro +pedido.estado.esperando_aceptacion_ferro=Esperando aceptación de ferro +pedido.estado.ferro_cliente=Esperando aprobación de ferro +pedido.estado.produccion=Producción +pedido.estado.terminado=Terminado +pedido.estado.cancelado=Cancelado + +pedido.module-title=Pedidos +pedido.pedido=Pedido +pedido.fecha-entrega=Fecha de entrega +pedido.cancelar=Cancelar pedido +pedido.update-estado=Actualizar estado +pedido.maquetacion_finalizada=Maquetación finalizada +pedido.ferro=Ferro +pedido.cubierta=Cubierta +pedido.tapa=Tapa +pedido.aceptar_ferro=Aceptar ferro +pedido.shipping-addresses=Direcciones de envío +pedido.prueba=Prueba + +pedido.table.id=Num. Pedido +pedido.table.cliente=Cliente +pedido.table.fecha=Fecha +pedido.table.importe=Importe +pedido.table.estado=Estado +pedido.table.acciones=Acciones + +pedido.view.tirada=Tirada +pedido.view.view-presupuesto=Ver presupuesto +pedido.view.aceptar-ferro=Aceptar ferro +pedido.view.ferro-download=Descargar ferro +pedido.view.cub-download=Descargar cubierta +pedido.view.tapa-download=Descargar tapa + +pedido.errors.linea-not-found=No se ha encontrado la línea de pedido. +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.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.success.estado-actualizado=Estado del pedido actualizado correctamente. +pedido.success.same-estado=Sin cambios en el estado. \ No newline at end of file diff --git a/src/main/resources/i18n/presupuesto_es.properties b/src/main/resources/i18n/presupuesto_es.properties index 84b1069..16f1e7e 100644 --- a/src/main/resources/i18n/presupuesto_es.properties +++ b/src/main/resources/i18n/presupuesto_es.properties @@ -11,6 +11,12 @@ presupuesto.add-to-presupuesto=Añadir al presupuesto presupuesto.calcular=Calcular presupuesto.add=Añadir presupuesto presupuesto.guardar=Guardar +presupuesto.duplicar=Duplicar +presupuesto.reimprimir=Reimprimir +presupuesto.reimpresion=Reimpresión +presupuesto.editar=Editar +presupuesto.ver=Ver +presupuesto.borrar=Eliminar presupuesto.add-to-cart=Añadir a la cesta presupuesto.nav.presupuestos-cliente=Presupuestos cliente @@ -37,6 +43,8 @@ presupuesto.tabla.region=Región presupuesto.tabla.ciudad=Ciudad presupuesto.tabla.acciones=Acciones +presupuesto.comentario-administrador=Comentarios + # Pestaña datos generales de presupuesto presupuesto.informacion-libro=Información del libro presupuesto.datos-generales-descripcion=Datos generales del presupuesto @@ -130,6 +138,7 @@ presupuesto.papel-guardas=Papel de guardas presupuesto.guardas-impresas=Guardas impresas presupuesto.no=No presupuesto.cabezada=Cabezada +presupuesto.cabezada-sin-cabezada=Sin cabezada presupuesto.cabezada-blanca=Blanca presupuesto.cabezada-verde=Verde presupuesto.cabezada-azul=Azul @@ -294,6 +303,30 @@ presupuesto.error.delete-permission-denied=No se puede eliminar: permiso denegad presupuesto.error.delete-not-found=No se puede eliminar: presupuesto no encontrado. presupuesto.error.delete-not-draft=Solo se pueden eliminar presupuestos en estado Borrador. +# Mensajes de duplicar presupuesto +presupuesto.duplicar.title=Duplicar presupuesto +presupuesto.duplicar.confirm=Si, DUPLICAR +presupuesto.duplicar.cancelar=Cancelar +presupuesto.duplicar.text=¿Está seguro de que desea duplicar este presupuesto?
Se creará una copia exacta del mismo en estado Borrador con el título introducido a continuación. +presupuesto.duplicar.required=El título es obligatorio. +presupuesto.duplicar.success.title=Presupuesto duplicado +presupuesto.duplicar.success.text=El presupuesto ha sido duplicado con éxito. +presupuesto.duplicar.aceptar=Aceptar +presupuesto.duplicar.error.title=Error al duplicar presupuesto +presupuesto.duplicar.error.internal=No se puede duplicar: error interno. + +# Mensajes de reimprimir presupuesto +presupuesto.reimprimir.title=Reimprimir presupuesto +presupuesto.reimprimir.confirm=Si, REIMPRIMIR +presupuesto.reimprimir.cancelar=Cancelar +presupuesto.reimprimir.text=¿Está seguro de que desea reimprimir este presupuesto?
Se generará una nuevo presupuesto usando los mismos ficheros para su impresión. +presupuesto.reimprimir.success.title=Presupuesto generado +presupuesto.reimprimir.success.text=El presupuesto ha sido generado con éxito. +presupuesto.reimprimir.aceptar=Aceptar +presupuesto.reimprimir.error.title=Error al generar el presupuesto +presupuesto.reimprimir.error.internal=No se puede generar el nuevo presupuesto: error interno. + + # Añadir presupuesto presupuesto.add.tipo=Tipo de presupuesto presupuesto.add.anonimo=Anónimo diff --git a/src/main/resources/static/assets/images/billin_address.gif b/src/main/resources/static/assets/images/billin_address.gif new file mode 100644 index 0000000..e5c69d0 Binary files /dev/null and b/src/main/resources/static/assets/images/billin_address.gif differ diff --git a/src/main/resources/static/assets/images/billing_address2.gif b/src/main/resources/static/assets/images/billing_address2.gif new file mode 100644 index 0000000..a946bbf Binary files /dev/null and b/src/main/resources/static/assets/images/billing_address2.gif differ diff --git a/src/main/resources/static/assets/images/billing_address2.gif.gif b/src/main/resources/static/assets/images/billing_address2.gif.gif new file mode 100644 index 0000000..73b0dd8 Binary files /dev/null and b/src/main/resources/static/assets/images/billing_address2.gif.gif differ diff --git a/src/main/resources/static/assets/images/delivery-truck.gif b/src/main/resources/static/assets/images/delivery-truck.gif new file mode 100644 index 0000000..88485ab Binary files /dev/null and b/src/main/resources/static/assets/images/delivery-truck.gif differ diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js b/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js index f85f679..b17ee19 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js @@ -21,6 +21,7 @@ $(() => { $(this).find('.direccion-id').attr('name', 'direcciones[' + i + '].id'); $(this).find('.direccion-cp').attr('name', 'direcciones[' + i + '].cp'); $(this).find('.direccion-pais-code3').attr('name', 'direcciones[' + i + '].paisCode3'); + $(this).find('.is-palets').attr('name', 'direcciones[' + i + '].isPalets'); if ($(this).find('.presupuesto-id').length > 0 && $(this).find('.presupuesto-id').val() !== null && $(this).find('.presupuesto-id').val() !== "") $(this).find('.presupuesto-id').attr('name', 'direcciones[' + i + '].presupuestoId'); diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js index cd97df1..c1ece71 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js @@ -140,6 +140,7 @@ $(() => { let uri = `/checkout/get-address/${direccionId}`; const response = await fetch(uri); if (response.ok) { + $('#dirFactId').val(direccionId); const html = await response.text(); $('#direccion-div').append(html); $('#addBillingAddressBtn').addClass('d-none'); diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-admin.js b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-admin.js new file mode 100644 index 0000000..48616ef --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-admin.js @@ -0,0 +1,70 @@ +import { normalizeNumericFilter } from '../utils.js'; + +$(() => { + + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + const language = document.documentElement.lang || 'es-ES'; + + const tablePedidos = $('#pedidos-datatable').DataTable({ + processing: true, + serverSide: true, + orderCellsTop: true, + pageLength: 50, + lengthMenu: [10, 25, 50, 100, 500], + order: [[5, 'desc']], // Ordena por fecha por defecto + language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, + responsive: true, + dom: 'lBrtip', + buttons: { + dom: { + button: { + className: 'btn btn-sm btn-outline-primary me-1' + }, + buttons: [ + { extend: 'copy' }, + { extend: 'csv' }, + { extend: 'excel' }, + { extend: 'pdf' }, + { extend: 'print' }, + { extend: 'colvis' } + ], + } + }, + ajax: { + url: '/pedidos/datatable', + method: 'GET', + }, + order: [[0, 'desc']], + columns: [ + { data: 'id', name: 'id', orderable: true }, + { data: 'cliente', name: 'createdBy.fullName', orderable: true }, + { data: 'created_at', name: 'createdAt', orderable: true }, + { data: 'total', name: 'total', orderable: true }, + { data: 'estado', name: 'estado', orderable: true }, + { data: 'actions', name: 'actions', orderable: false, searchable: false } + + ], + }); + + tablePedidos.on("keyup change", ".input-filter", function () { + const colName = $(this).data("col"); + const colIndex = tablePedidos.settings()[0].aoColumns.findIndex(c => c.name === colName); + + if (colIndex >= 0) { + tablePedidos + .column(colIndex) + .search(normalizeNumericFilter(this.value)) + .draw(); + } + }); + +}) \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-common.js b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-common.js new file mode 100644 index 0000000..aedea13 --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-common.js @@ -0,0 +1,97 @@ +$(() => { + $(document).on('click', '.btn-view', function () { + let pedidoId = $(this).data('id'); + let url = `/pedidos/view/${pedidoId}`; + window.location.href = url; + }); + + $(document).on('click', '.btn-pay', async function () { + const pedidoId = parseInt($(this).data('id')); + const amount = parseInt($(this).data('amount')); + + const result = await swalMetodoPago(); + if (!result.isConfirmed) return; + + const method = result.value; + + // crear y enviar un form normal (NO ajax) + const form = document.createElement('form'); + form.method = 'POST'; + form.action = '/pagos/redsys/reintentar'; + + // CSRF (Spring Security) + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfParam = document.querySelector('meta[name="_csrf_parameter"]')?.getAttribute('content') || '_csrf'; + + const add = (name, value) => { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = name; + input.value = String(value); + form.appendChild(input); + }; + + add('amountCents', amount); + add('orderId', pedidoId); + add('method', method); + if (csrfToken) add(csrfParam, csrfToken); + + document.body.appendChild(form); + form.submit(); + }); + + + + function swalMetodoPago() { + return Swal.fire({ + title: window.languageBundle['checkout.payment'] || 'Método de pago', + width: '32rem', + html: ` +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ `, + focusConfirm: false, + showCancelButton: true, + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary me-2', + cancelButton: 'btn btn-light' + }, + confirmButtonText: window.languageBundle['app.aceptar'] || 'Aceptar', + cancelButtonText: window.languageBundle['app.cancelar'] || 'Cancelar', + + preConfirm: () => { + const selected = document.querySelector('input[name="paymentMethod"]:checked'); + if (!selected) { + Swal.showValidationMessage( + window.languageBundle['checkout.error.select-method'] || 'Selecciona un método de pago' + ); + return false; + } + return selected.value; + } + }); + } + + +}) \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view-admin.js b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view-admin.js new file mode 100644 index 0000000..ddc23ae --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view-admin.js @@ -0,0 +1,146 @@ +$(() => { + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + const language = document.documentElement.lang || 'es-ES'; + + + $(document).on('click', '.update-status-item', function () { + const lineaId = $(this).data('linea-id'); + if (!lineaId) { + console.error('No se ha encontrado el ID de la línea del pedido.'); + return; + } + + // Llamada AJAX para actualizar el estado del pedido + $.ajax({ + url: `/pedidos/linea/${lineaId}/update-status`, + 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 { + const estadoSpan = $(`.estado-linea[data-linea-id='${lineaId}']`); + if (estadoSpan.length) { + estadoSpan.text(response.state); + } + if (response.stateKey === 'terminado' || response.stateKey === 'cancelado') { + $(`.update-estado-button[data-linea-id='${lineaId}']`) + .closest('.update-estado-button') + .addClass('d-none'); + } + Swal.fire({ + icon: 'success', + title: response.message || "Exito", + 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 actualizar el estado del 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 + } + }); + } + }); + }); + + $(document).on('click', '.maquetacion-ok', function () { + + const lineaId = $(this).data('linea-id'); + if (!lineaId) { + console.error('No se ha encontrado el ID de la línea del pedido.'); + return; + } + + // Llamada AJAX para marcar la maquetación como OK + $.ajax({ + url: `/pedidos/linea/${lineaId}/update-maquetacion`, + 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 { + const estadoSpan = $(`.estado-linea[data-linea-id='${lineaId}']`); + if (estadoSpan.length) { + estadoSpan.text(response.state); + // hide the maquetacion-ok button + $(`.maquetacion-ok[data-linea-id='${lineaId}']`) + .closest('.maquetacion-ok-button') + .addClass('d-none'); + } + Swal.fire({ + icon: 'success', + title: response.message || "Exito", + timer: 1800, + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary me-2', + cancelButton: 'btn btn-light' + }, + showConfirmButton: false + }); + } + }, + error: function (xhr, status, error) { + console.error('Error al actualizar la maquetación del 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 + } + }); + } + }); + + }); +}) \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view.js b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view.js new file mode 100644 index 0000000..17afcaf --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos-view.js @@ -0,0 +1,86 @@ +$(() => { + if ($(".btn-download-ferro").length) { + $(document).on('click', '.btn-download-ferro', function () { + const lineaId = $(this).data('linea-id'); + + window.open(`/pedidos/linea/${lineaId}/download-ferro`, '_blank'); + }); + } + if ($(".btn-download-cub").length) { + $(document).on('click', '.btn-download-cub', function () { + const lineaId = $(this).data('linea-id'); + + window.open(`/pedidos/linea/${lineaId}/download-cub`, '_blank'); + }); + } + if ($(".btn-download-tapa").length) { + $(document).on('click', '.btn-download-tapa', function () { + const lineaId = $(this).data('linea-id'); + + window.open(`/pedidos/linea/${lineaId}/download-tapa`, '_blank'); + }); + } + + if ($(".btn-aceptar-ferro").length) { + $(document).on('click', '.btn-aceptar-ferro', function () { + const lineaId = $(this).data('linea-id'); + + $.ajax({ + url: `/pedidos/linea/${lineaId}/aceptar-ferro`, + 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 { + const estadoSpan = $(`.estado-linea[data-linea-id='${lineaId}']`); + if (estadoSpan.length) { + estadoSpan.text(response.state); + } + $(`.btn-aceptar-ferro[data-linea-id='${lineaId}']`) + .closest('.btn-aceptar-ferro') + .addClass('d-none'); + Swal.fire({ + icon: 'success', + title: response.message || "Exito", + 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 aceptar el ferro del 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 + } + }); + } + }); + }); + } +}); \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos.js b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos.js new file mode 100644 index 0000000..714e19d --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pedidos/pedidos.js @@ -0,0 +1,68 @@ +import { normalizeNumericFilter } from '../utils.js'; + +$(() => { + + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + const language = document.documentElement.lang || 'es-ES'; + + const tablePedidos = $('#pedidos-datatable').DataTable({ + processing: true, + serverSide: true, + orderCellsTop: true, + pageLength: 50, + lengthMenu: [10, 25, 50, 100, 500], + order: [[5, 'desc']], // Ordena por fecha por defecto + language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, + responsive: true, + dom: 'lBrtip', + buttons: { + dom: { + button: { + className: 'btn btn-sm btn-outline-primary me-1' + }, + buttons: [ + { extend: 'copy' }, + { extend: 'csv' }, + { extend: 'excel' }, + { extend: 'pdf' }, + { extend: 'print' }, + { extend: 'colvis' } + ], + } + }, + ajax: { + url: '/pedidos/datatable', + method: 'GET', + }, + order: [[0, 'desc']], + columns: [ + { data: 'id', name: 'id', orderable: true }, + { data: 'created_at', name: 'createdAt', orderable: true }, + { data: 'total', name: 'total', orderable: true }, + { data: 'estado', name: 'estado', orderable: true }, + { data: 'actions', name: 'actions', orderable: false, searchable: false } + + ], + }); + + tablePedidos.on("keyup change", ".input-filter", function () { + const colName = $(this).data("col"); + const colIndex = tablePedidos.settings()[0].aoColumns.findIndex(c => c.name === colName); + + if (colIndex >= 0) { + tablePedidos + .column(colIndex) + .search(normalizeNumericFilter(this.value)) + .draw(); + } + }); +}) \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/text-editor.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/text-editor.js new file mode 100644 index 0000000..edf997d --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/text-editor.js @@ -0,0 +1,82 @@ +$(() => { + var snowEditor = document.querySelectorAll(".snow-editor"); + if (snowEditor) { + Array.from(snowEditor).forEach(function (item) { + var snowEditorData = {}; + var issnowEditorVal = item.classList.contains("snow-editor"); + if (issnowEditorVal == true) { + snowEditorData.theme = 'snow', + snowEditorData.modules = { + 'toolbar': [ + [{ + 'font': [] + }, { + 'size': [] + }], + ['bold', 'italic', 'underline', 'strike'], + [{ + 'color': [] + }, { + 'background': [] + }], + [{ + 'script': 'super' + }, { + 'script': 'sub' + }], + [{ + 'header': [false, 1, 2, 3, 4, 5, 6] + }, 'blockquote', 'code-block'], + [{ + 'list': 'ordered' + }, { + 'list': 'bullet' + }, { + 'indent': '-1' + }, { + 'indent': '+1' + }], + ['direction', { + 'align': [] + }] + ] + } + } + var quill = new Quill(item, snowEditorData); + + var initialContent = item.dataset.contenido || ""; + // Contenido inicial desde Thymeleaf + var initialContent = item.dataset.contenido || ""; + if (initialContent) { + if(initialContent.trim() !== "" && initialContent.trim() !== "


") + $('.badge-comentario').removeClass('d-none'); + quill.clipboard.dangerouslyPasteHTML(initialContent); + } + + quill.root.addEventListener("blur", function () { + + let contenido = quill.root.innerHTML; + if(contenido.trim() !== "" && contenido.trim() !== "


"){ + $('.badge-comentario').removeClass('d-none'); + } else { + $('.badge-comentario').addClass('d-none'); + } + if ($('#presupuesto_id').length > 0 && $('#presupuesto_id').val() !== "") { + + $.ajax({ + url: "/presupuesto/" + $('#presupuesto_id').val() + "/comentario", + method: "POST", + data: { + comentario: contenido + }, + success: function () { + + } + }); + } + }); + }); + } + + +}); \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js index 7c76b4d..7eb71f2 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js @@ -402,8 +402,6 @@ export default class PresupuestoWizard { ...this.#getInteriorData(), ...this.#getCubiertaData(), selectedTirada: this.formData.selectedTirada - - }; const sobrecubierta = data.sobrecubierta; @@ -927,6 +925,7 @@ export default class PresupuestoWizard { this.#changeTab('pills-general-data'); } else { const maxSolapas = data.solapas ?? 120; + const lomo = data.lomo ?? 0; $('.solapas-presupuesto').attr('max', maxSolapas); $('.max-solapa-text').text(function (_, textoActual) { return textoActual.replace(/\d+/, maxSolapas); @@ -951,6 +950,20 @@ export default class PresupuestoWizard { this.acabadoSobrecubierta.val(this.formData.cubierta.sobrecubierta.acabado); this.fajaSobrecubierta.val(this.formData.cubierta.faja.acabado); + if(lomo < 10){ + this.formData.cubierta.cabezada = "NOCAB"; + this.cabezada.val("NOCAB"); + this.cabezada.prop("disabled", true); + if(this.formData.cubierta.tipoCubierta === 'tapaDuraLomoRedondo'){ + this.formData.cubierta.tipoCubierta = 'tapaDura'; + } + $("#tapaDuraLomoRedondo").addClass("d-none"); + } + else{ + this.cabezada.prop("disabled", false); + $("#tapaDuraLomoRedondo").removeClass("d-none"); + } + this.#loadCubiertaData(); this.summaryTableCubierta.removeClass('d-none'); if (this.sobrecubierta.hasClass('active')) { @@ -1683,7 +1696,7 @@ export default class PresupuestoWizard { const body = { presupuesto: this.#getPresupuestoData(), - save: !this.opts.canSave, + save: this.opts.canSave, mode: this.opts.mode, servicios: servicios, datosMaquetacion: this.formData.servicios.datosMaquetacion, diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js new file mode 100644 index 0000000..87f3b22 --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/duplicate-reprint.js @@ -0,0 +1,16 @@ +import { duplicar, reimprimir } from './presupuesto-utils.js'; + +$(()=> { + $('.duplicar-btn').on('click', function(e) { + e.preventDefault(); + const id = $('#presupuesto_id').val(); + const tituloOriginal = $('#titulo').text().trim() == '' ? $('#titulo').val().trim() : $('#titulo').text().trim(); + duplicar(id, tituloOriginal); + }) + + $('.reimprimir-btn').on('click', function(e) { + e.preventDefault(); + const id = $('#presupuesto_id').val(); + reimprimir(id); + }) +}) \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/list.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/list.js index d7af401..082e28e 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/list.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/list.js @@ -1,4 +1,4 @@ -import { preguntarTipoPresupuesto } from './presupuesto-utils.js'; +import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-utils.js'; (() => { // si jQuery está cargado, añade CSRF a AJAX @@ -200,6 +200,26 @@ import { preguntarTipoPresupuesto } from './presupuesto-utils.js'; } }); + $('#presupuestos-clientes-datatable').on('click', '.btn-duplicate-privado', function (e) { + + e.preventDefault(); + const id = $(this).data('id'); + let data = table_clientes.row($(this).parents('tr')).data(); + const tituloOriginal = data.titulo; + + duplicar(id, tituloOriginal); + }); + + $('#presupuestos-clientes-datatable').on('click', '.btn-reprint-privado', function (e) { + + e.preventDefault(); + const id = $(this).data('id'); + let data = table_clientes.row($(this).parents('tr')).data(); + const tituloOriginal = data.titulo; + + reimprimir(id, tituloOriginal); + }); + $('#presupuestos-clientes-datatable').on('click', '.btn-delete-privado', function (e) { e.preventDefault(); diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/presupuesto-utils.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/presupuesto-utils.js index 5b42345..0bcb50d 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/presupuesto-utils.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/presupuesto-utils.js @@ -75,3 +75,171 @@ export async function preguntarTipoPresupuesto() { } }).then((r) => (r.isConfirmed ? r.value : null)); } + +export function duplicar(id, titulo) { + + // swal with input + Swal.fire({ + title: window.languageBundle.get(['presupuesto.duplicar.title']) || 'Duplicar presupuesto', + html: window.languageBundle.get(['presupuesto.duplicar.text']) || `¿Deseas duplicar el presupuesto "${titulo}"?`, + icon: 'question', + input: 'text', + inputValue: `[D] ${titulo}`, + showCancelButton: true, + confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.confirm']) || 'Sí, duplicar', + cancelButtonText: window.languageBundle.get(['presupuesto.duplicar.cancelar']) || 'Cancelar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar + cancelButton: 'btn btn-light' // clases para cancelar + }, + inputValidator: (value) => { + if (!value) { + return window.languageBundle.get(['presupuesto.duplicar.required']) || 'El título no puede estar vacío'; + } + }, + }).then((result) => { + if (result.isConfirmed) { + + const tituloNuevo = result.value; + + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + $.ajax({ + url: `/presupuesto/api/duplicar/${id}`, + data: { + titulo: tituloNuevo, + }, + method: 'POST', + success: function (response) { + + if (response.id && response.id > 0) { + + Swal.fire({ + title: window.languageBundle.get(['presupuesto.duplicar.success.title']) || 'Presupuesto duplicado', + text: window.languageBundle.get(['presupuesto.duplicar.success.text']) || `El presupuesto "${titulo}" ha sido duplicado correctamente.`, + icon: 'success', + confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }).then(() => { + // Recargar la página o redirigir a la lista de presupuestos + window.location.href = '/presupuesto/edit/' + response.id; + }); + } else { + Swal.fire({ + title: window.languageBundle.get(['presupuesto.duplicar.error.title']) || 'Error al duplicar', + text: response.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.', + icon: 'error', + confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }); + } + }, + error: function (xhr) { + Swal.fire({ + title: window.languageBundle.get(['presupuesto.duplicar.error.title']) || 'Error al duplicar', + text: xhr.responseJSON?.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.', + icon: 'error', + confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }); + } + + }); + }; + }); +} + +export function reimprimir(id) { +Swal.fire({ + title: window.languageBundle.get(['presupuesto.reimprimir.title']) || 'Reimprimir presupuesto', + html: window.languageBundle.get(['presupuesto.reimprimir.text']) || `¿Deseas reimprimir el presupuesto?`, + icon: 'question', + showCancelButton: true, + confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.confirm']) || 'Sí, reimprimir', + cancelButtonText: window.languageBundle.get(['presupuesto.reimprimir.cancelar']) || 'Cancelar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar + cancelButton: 'btn btn-light' // clases para cancelar + }, + }).then((result) => { + if (result.isConfirmed) { + + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + $.ajax({ + url: `/presupuesto/api/reimprimir/${id}`, + method: 'POST', + success: function (response) { + + if (response.id && response.id > 0) { + + Swal.fire({ + title: window.languageBundle.get(['presupuesto.reimprimir.success.title']) || 'Presupuesto reimpreso', + text: window.languageBundle.get(['presupuesto.reimprimir.success.text']) || `El presupuesto ha sido reimpreso correctamente.`, + icon: 'success', + confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }).then(() => { + // Recargar la página o redirigir a la lista de presupuestos + window.location.href = '/presupuesto/edit/' + response.id; + }); + } else { + Swal.fire({ + title: window.languageBundle.get(['presupuesto.reimprimir.error.title']) || 'Error al reimprimir', + text: response.message || window.languageBundle.get(['presupuesto.reimprimir.error.internal']) || 'Ha ocurrido un error al reimprimir el presupuesto.', + icon: 'error', + confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }); + } + }, + error: function (xhr) { + Swal.fire({ + title: window.languageBundle.get(['presupuesto.reimprimir.error.title']) || 'Error al reimprimir', + text: xhr.responseJSON?.message || window.languageBundle.get(['presupuesto.reimprimir.error.internal']) || 'Ha ocurrido un error al reimprimir el presupuesto.', + icon: 'error', + confirmButtonText: window.languageBundle.get(['presupuesto.reimprimir.aceptar']) || 'Aceptar', + buttonsStyling: false, + customClass: { + confirmButton: 'btn btn-secondary' // clases para el botón confirmar + }, + }); + } + + }); + }; + }); +} diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/resumen-view.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/resumen-view.js index b93bdb8..e8ea184 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/resumen-view.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestos/resumen-view.js @@ -31,7 +31,7 @@ $(() => { e.preventDefault(); // obtén el id de donde lo tengas (data-attr o variable global) - const id = $('#presupuesto-id').val(); + const id = $('#presupuesto_id').val(); const url = `/api/pdf/presupuesto/${id}?mode=download`; @@ -47,7 +47,7 @@ $(() => { $('.add-cart-btn').on('click', async () => { - const presupuestoId = $('#presupuesto-id').val(); + const presupuestoId = $('#presupuesto_id').val(); const res = await $.ajax({ url: `/cart/add/${presupuestoId}`, method: 'POST', diff --git a/src/main/resources/templates/imprimelibros/checkout/_summary.html b/src/main/resources/templates/imprimelibros/checkout/_summary.html index 83a1309..85ce845 100644 --- a/src/main/resources/templates/imprimelibros/checkout/_summary.html +++ b/src/main/resources/templates/imprimelibros/checkout/_summary.html @@ -50,6 +50,7 @@ + diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccionCard.html b/src/main/resources/templates/imprimelibros/direcciones/direccionCard.html index bc71270..c12e8a5 100644 --- a/src/main/resources/templates/imprimelibros/direcciones/direccionCard.html +++ b/src/main/resources/templates/imprimelibros/direcciones/direccionCard.html @@ -7,7 +7,7 @@ - +
diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccionEnvioCard.html b/src/main/resources/templates/imprimelibros/direcciones/direccionEnvioCard.html new file mode 100644 index 0000000..bc12b5e --- /dev/null +++ b/src/main/resources/templates/imprimelibros/direcciones/direccionEnvioCard.html @@ -0,0 +1,40 @@ +
+
+ + +
+
+ +
+
+ + + + + +
+
+
+ + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccionFacturacionCard.html b/src/main/resources/templates/imprimelibros/direcciones/direccionFacturacionCard.html new file mode 100644 index 0000000..9a4a251 --- /dev/null +++ b/src/main/resources/templates/imprimelibros/direcciones/direccionFacturacionCard.html @@ -0,0 +1,28 @@ +
+
+ +
+
+
+
+ Billing Address +
+
+ + + + + + + + +
+
+
+ +
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/partials/sidebar.html b/src/main/resources/templates/imprimelibros/partials/sidebar.html index e811ed6..a972ae7 100644 --- a/src/main/resources/templates/imprimelibros/partials/sidebar.html +++ b/src/main/resources/templates/imprimelibros/partials/sidebar.html @@ -43,6 +43,11 @@ Presupuestos +
+ +
+
+
+

+ + +

+ +
+
+
+
+
+
+ +
+
+
diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html index 8401695..9bbb740 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html @@ -10,6 +10,8 @@ + @@ -19,62 +21,77 @@
- -
+ +
-
-
-
-
-
-
+
- - - + + - -
- -
-
- -
-
-
- +
+
-
- + + + + + + + +
+
-
- - -
+
+ +
+
+
+ +
+
+ +
+
+ + + + + + + +
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente-user.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente-user.html index 8d17793..b07d34f 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente-user.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente-user.html @@ -12,7 +12,7 @@ Estado Total con IVA Actualizado el - Acciones + Acciones diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html index 7c400e7..d8e585f 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html @@ -13,7 +13,7 @@ Estado Total con IVA Actualizado el - Acciones + Acciones diff --git a/src/test/java/com/imprimelibros/erp/cart/envioCarroTest.java b/src/test/java/com/imprimelibros/erp/cart/envioCarroTest.java index faf6969..a2b1660 100644 --- a/src/test/java/com/imprimelibros/erp/cart/envioCarroTest.java +++ b/src/test/java/com/imprimelibros/erp/cart/envioCarroTest.java @@ -18,7 +18,7 @@ public class envioCarroTest { void addPedido(){ Locale locale = Locale.forLanguageTag("es-ES"); - cartService.crearPedido(carritoId, locale); + //cartService.crearPedido(carritoId, null, locale); } diff --git a/src/test/java/com/imprimelibros/erp/presupuesto/savePresupuestosTest.java b/src/test/java/com/imprimelibros/erp/presupuesto/savePresupuestosTest.java index f8d10f4..4934d2f 100644 --- a/src/test/java/com/imprimelibros/erp/presupuesto/savePresupuestosTest.java +++ b/src/test/java/com/imprimelibros/erp/presupuesto/savePresupuestosTest.java @@ -17,10 +17,10 @@ public class savePresupuestosTest { @Test void testGuardarPresupuesto() { Locale locale = new Locale("es", "ES"); - Long resultado = cartService.crearPedido(9L, locale); + //Long resultado = cartService.crearPedido(9L, null, locale); System.out.println("📦 Presupuesto guardado:"); - System.out.println(resultado); + //System.out.println(resultado); // Aquí irían las aserciones para verificar que el presupuesto se guardó correctamente }