更新项目工作流

This commit is contained in:
tzy 2026-04-12 10:11:15 +08:00
parent 84fb4742d3
commit 87f34772b8
56 changed files with 4191 additions and 2445 deletions

View File

@ -1,4 +1,4 @@
2026-02-13 09:12:26 [reactor-http-nio-10] INFO d.c.b.a.s.services.StatusUpdater - Couldn't retrieve status for Instance(id=08432fb0b14f, version=2, registration=Registration(name=RuoYi-Vue-Plus, managementUrl=http://192.168.5.200:8033/actuator, healthUrl=http://192.168.5.200:8033/actuator/health, serviceUrl=http://192.168.5.200:8033/, source=http-api), registered=true, statusInfo=StatusInfo(status=UP, details={db={status=UP, details={database=MySQL, validationQuery=isValid()}}, diskSpace={status=UP, details={total=974330859520, free=702342062080, threshold=10485760, exists=true}}, ping={status=UP}, redis={status=UP, details={version=3.0.503}}}), statusTimestamp=2026-02-12T09:50:54.228Z, info=Info(values={}), endpoints=Endpoints(endpoints={caches=Endpoint(id=caches, url=http://192.168.5.200:8033/actuator/caches), loggers=Endpoint(id=loggers, url=http://192.168.5.200:8033/actuator/loggers), logfile=Endpoint(id=logfile, url=http://192.168.5.200:8033/actuator/logfile), health=Endpoint(id=health, url=http://192.168.5.200:8033/actuator/health), env=Endpoint(id=env, url=http://192.168.5.200:8033/actuator/env), heapdump=Endpoint(id=heapdump, url=http://192.168.5.200:8033/actuator/heapdump), scheduledtasks=Endpoint(id=scheduledtasks, url=http://192.168.5.200:8033/actuator/scheduledtasks), mappings=Endpoint(id=mappings, url=http://192.168.5.200:8033/actuator/mappings), startup=Endpoint(id=startup, url=http://192.168.5.200:8033/actuator/startup), beans=Endpoint(id=beans, url=http://192.168.5.200:8033/actuator/beans), configprops=Endpoint(id=configprops, url=http://192.168.5.200:8033/actuator/configprops), threaddump=Endpoint(id=threaddump, url=http://192.168.5.200:8033/actuator/threaddump), metrics=Endpoint(id=metrics, url=http://192.168.5.200:8033/actuator/metrics), conditions=Endpoint(id=conditions, url=http://192.168.5.200:8033/actuator/conditions), info=Endpoint(id=info, url=http://192.168.5.200:8033/actuator/info)}), buildVersion=null, tags=Tags(values={}))
2026-04-12 09:48:29 [reactor-http-nio-16] INFO d.c.b.a.s.services.StatusUpdater - Couldn't retrieve status for Instance(id=08432fb0b14f, version=47, registration=Registration(name=RuoYi-Vue-Plus, managementUrl=http://192.168.5.200:8033/actuator, healthUrl=http://192.168.5.200:8033/actuator/health, serviceUrl=http://192.168.5.200:8033/, source=http-api), registered=true, statusInfo=StatusInfo(status=UP, details={db={status=UP, details={database=MySQL, validationQuery=isValid()}}, diskSpace={status=UP, details={total=974330859520, free=697055969280, threshold=10485760, exists=true}}, ping={status=UP}, redis={status=UP, details={version=3.0.503}}}), statusTimestamp=2026-04-11T09:53:14.781Z, info=Info(values={}), endpoints=Endpoints(endpoints={caches=Endpoint(id=caches, url=http://192.168.5.200:8033/actuator/caches), loggers=Endpoint(id=loggers, url=http://192.168.5.200:8033/actuator/loggers), logfile=Endpoint(id=logfile, url=http://192.168.5.200:8033/actuator/logfile), health=Endpoint(id=health, url=http://192.168.5.200:8033/actuator/health), env=Endpoint(id=env, url=http://192.168.5.200:8033/actuator/env), heapdump=Endpoint(id=heapdump, url=http://192.168.5.200:8033/actuator/heapdump), mappings=Endpoint(id=mappings, url=http://192.168.5.200:8033/actuator/mappings), scheduledtasks=Endpoint(id=scheduledtasks, url=http://192.168.5.200:8033/actuator/scheduledtasks), startup=Endpoint(id=startup, url=http://192.168.5.200:8033/actuator/startup), configprops=Endpoint(id=configprops, url=http://192.168.5.200:8033/actuator/configprops), beans=Endpoint(id=beans, url=http://192.168.5.200:8033/actuator/beans), threaddump=Endpoint(id=threaddump, url=http://192.168.5.200:8033/actuator/threaddump), metrics=Endpoint(id=metrics, url=http://192.168.5.200:8033/actuator/metrics), conditions=Endpoint(id=conditions, url=http://192.168.5.200:8033/actuator/conditions), info=Endpoint(id=info, url=http://192.168.5.200:8033/actuator/info)}), buildVersion=null, tags=Tags(values={}))
org.springframework.web.reactive.function.client.WebClientRequestException: Connection refused: no further information: /192.168.5.200:8033; nested exception is io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.5.200:8033
at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:141)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
@ -76,5 +76,5 @@ Caused by: java.net.ConnectException: Connection refused: no further information
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:750)
2026-02-13 09:12:26 [reactor-http-nio-10] INFO c.r.m.admin.notifier.CustomNotifier - Instance Status Change: [RuoYi-Vue-Plus],[08432fb0b14f],[OFFLINE]
2026-02-13 09:13:02 [reactor-http-nio-12] INFO c.r.m.admin.notifier.CustomNotifier - Instance Status Change: [RuoYi-Vue-Plus],[08432fb0b14f],[UP]
2026-04-12 09:48:29 [reactor-http-nio-16] INFO c.r.m.admin.notifier.CustomNotifier - Instance Status Change: [RuoYi-Vue-Plus],[08432fb0b14f],[OFFLINE]
2026-04-12 09:48:57 [reactor-http-nio-2] INFO c.r.m.admin.notifier.CustomNotifier - Instance Status Change: [RuoYi-Vue-Plus],[08432fb0b14f],[UP]

View File

@ -1,27 +1,51 @@
2026-02-12 00:29:30 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=46s179ms400µs600ns).
2026-02-12 02:57:51 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=45s809ms35µs400ns).
2026-02-12 03:21:20 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=58s730ms424µs).
2026-02-12 17:50:07 [main] INFO c.x.job.admin.XxlJobAdminApplication - Starting XxlJobAdminApplication using Java 1.8.0_421 on PC-20250327GIRN with PID 32340 (F:\evo_k3cloud\ruoyi-extend\ruoyi-xxl-job-admin\target\classes started by Administrator in F:\evo_k3cloud)
2026-02-12 17:50:07 [main] INFO c.x.job.admin.XxlJobAdminApplication - The following 1 profile is active: "dev"
2026-02-12 17:50:11 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 9100 (http)
2026-02-12 17:50:11 [main] INFO o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-9100"]
2026-02-12 17:50:11 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2026-02-12 17:50:11 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.74]
2026-02-12 17:50:11 [main] INFO o.a.c.c.C.[.[.[/xxl-job-admin] - Initializing Spring embedded WebApplicationContext
2026-02-12 17:50:11 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 4328 ms
2026-02-12 17:50:12 [main] INFO c.x.j.a.c.scheduler.XxlJobScheduler - >>>>>>>>> init xxl-job admin success.
2026-02-12 17:50:12 [xxl-job, admin JobFailMonitorHelper] INFO com.zaxxer.hikari.HikariDataSource - HikariCP - Starting...
2026-02-12 17:50:12 [main] INFO o.s.b.a.w.s.WelcomePageHandlerMapping - Adding welcome page template: index
2026-02-12 17:50:12 [xxl-job, admin JobFailMonitorHelper] INFO com.zaxxer.hikari.HikariDataSource - HikariCP - Start completed.
2026-02-12 17:50:13 [main] INFO o.s.b.a.e.web.EndpointLinksResolver - Exposing 14 endpoint(s) beneath base path '/actuator'
2026-02-12 17:50:13 [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-9100"]
2026-02-12 17:50:13 [main] INFO o.a.c.c.C.[.[.[/xxl-job-admin] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-02-12 17:50:13 [main] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2026-02-12 17:50:13 [main] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2026-02-12 17:50:13 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 9100 (http) with context path '/xxl-job-admin'
2026-02-12 17:50:13 [main] INFO c.x.job.admin.XxlJobAdminApplication - Started XxlJobAdminApplication in 7.259 seconds (JVM running for 8.794)
2026-02-12 17:50:15 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect. Further attempts are logged on DEBUG level
2026-02-12 17:50:17 [xxl-job, admin JobScheduleHelper#scheduleThread] INFO c.x.j.a.c.thread.JobScheduleHelper - >>>>>>>>> init xxl-job admin scheduler success.
2026-02-12 17:50:17 [xxl-job, admin JobScheduleHelper#scheduleThread] WARN c.x.j.a.c.thread.JobScheduleHelper - >>>>>>>>>>> xxl-job, schedule misfire, jobId = 2
2026-02-12 17:50:17 [xxl-job, admin JobScheduleHelper#scheduleThread] WARN c.x.j.a.c.thread.JobScheduleHelper - >>>>>>>>>>> xxl-job, schedule misfire, jobId = 23
2026-02-12 17:50:24 [registrationTask1] INFO d.c.b.a.c.r.ApplicationRegistrator - Application registered itself as cc38f3b5f0d4
2026-04-11 01:34:00 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 02:44:00 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 02:44:40 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 05:14:20 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 06:07:30 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 06:07:46 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=46s635ms171µs800ns).
2026-04-11 06:44:51 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 07:09:50 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 08:50:10 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 08:50:16 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=49s98ms91µs500ns).
2026-04-11 08:50:40 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 09:57:00 [registrationTask1] WARN d.c.b.a.c.r.ApplicationRegistrator - Failed to register application as Application(name=ruoyi-xxl-job-admin, managementUrl=http://192.168.5.200:9100/xxl-job-admin/actuator, healthUrl=http://192.168.5.200:9100/xxl-job-admin/actuator/health, serviceUrl=http://192.168.5.200:9100/xxl-job-admin) at spring-boot-admin ([http://localhost:9090/admin/instances]): I/O error on POST request for "http://localhost:9090/admin/instances": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out. Further attempts are logged on DEBUG level
2026-04-11 09:57:19 [HikariCP housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariCP - Thread starvation or clock leap detected (housekeeper delta=48s548ms12µs600ns).
2026-04-11 17:00:04 [xxl-job, admin JobFailMonitorHelper] ERROR c.x.j.a.c.alarm.impl.EmailJobAlarm - >>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:5974
org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535 Login fail. Account is abnormal, service is not open, password is incorrect, login frequency limited, or system is busy. More information at https://help.mail.qq.com/detail/108/1023
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:440)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:361)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:356)
at com.xxl.job.admin.core.alarm.impl.EmailJobAlarm.doAlarm(EmailJobAlarm.java:74)
at com.xxl.job.admin.core.alarm.JobAlarmer.alarm(JobAlarmer.java:52)
at com.xxl.job.admin.core.thread.JobFailMonitorHelper$1.run(JobFailMonitorHelper.java:66)
at java.lang.Thread.run(Thread.java:750)
Caused by: javax.mail.AuthenticationFailedException: 535 Login fail. Account is abnormal, service is not open, password is incorrect, login frequency limited, or system is busy. More information at https://help.mail.qq.com/detail/108/1023
at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:947)
at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:858)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:762)
at javax.mail.Service.connect(Service.java:342)
at org.springframework.mail.javamail.JavaMailSenderImpl.connectTransport(JavaMailSenderImpl.java:518)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:437)
... 6 common frames omitted
2026-04-11 17:30:06 [xxl-job, admin JobFailMonitorHelper] ERROR c.x.j.a.c.alarm.impl.EmailJobAlarm - >>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:5975
org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535 Login fail. Account is abnormal, service is not open, password is incorrect, login frequency limited, or system is busy. More information at https://help.mail.qq.com/detail/108/1023
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:440)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:361)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:356)
at com.xxl.job.admin.core.alarm.impl.EmailJobAlarm.doAlarm(EmailJobAlarm.java:74)
at com.xxl.job.admin.core.alarm.JobAlarmer.alarm(JobAlarmer.java:52)
at com.xxl.job.admin.core.thread.JobFailMonitorHelper$1.run(JobFailMonitorHelper.java:66)
at java.lang.Thread.run(Thread.java:750)
Caused by: javax.mail.AuthenticationFailedException: 535 Login fail. Account is abnormal, service is not open, password is incorrect, login frequency limited, or system is busy. More information at https://help.mail.qq.com/detail/108/1023
at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:947)
at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:858)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:762)
at javax.mail.Service.connect(Service.java:342)
at org.springframework.mail.javamail.JavaMailSenderImpl.connectTransport(JavaMailSenderImpl.java:518)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:437)
... 6 common frames omitted

File diff suppressed because one or more lines are too long

View File

@ -1,160 +1 @@
2026-02-13 07:15:53 [Thread-80] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:01 [xxl-job, JobThread-17-1770941700025] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:02 [xxl-job, JobThread-18-1770941700022] ERROR o.a.poi.ooxml.POIXMLDocumentPart - Skipped invalid entry /xl/sharedlinks.xml
2026-02-13 08:15:04 [xxl-job, JobThread-23-1770941700019] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:05 [xxl-job, JobThread-22-1770941700018] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:05 [xxl-job, JobThread-21-1770941700016] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:05 [xxl-job, JobThread-19-1770941700023] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:07 [xxl-job, JobThread-18-1770941700022] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:10 [xxl-job, JobThread-14-1770941700025] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:11 [xxl-job, JobThread-13-1770941700014] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:11 [xxl-job, JobThread-20-1770941700024] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:15:43 [xxl-job, JobThread-16-1770941700020] ERROR c.ruoyi.framework.aspectj.LogAspect - 异常信息:null
2026-02-13 08:58:31 [Thread-92] ERROR o.a.poi.ooxml.POIXMLDocumentPart - Skipped invalid entry /xl/sharedlinks.xml
2026-02-13 09:12:45 [main] ERROR c.b.m.core.MybatisConfiguration - mapper[com.ruoyi.system.mapper.ProcessRouteMapper.existsByProcessNoAndMaterialCode] is ignored, because it exists, maybe from xml file
2026-02-13 09:13:35 [Thread-78] ERROR c.r.s.c.KingdeeWorkCenterDataController - 发送钉钉文件失败: 机一工段生产延期数据_20260213091331.xlsx
java.lang.RuntimeException: 发送企业机器人文件消息失败
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:108)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendMassageDelayDate(KingdeeWorkCenterDataController.java:672)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$FastClassBySpringCGLIB$$812397e7.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$EnhancerBySpringCGLIB$$8eb42999.sendMassageDelayDate(<generated>)
at com.ruoyi.web.controller.dingtalk.CallbackController.lambda$processEvent$1(CallbackController.java:269)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 00096AFE-A0E4-7243-ACC2-7CE5CBA58014
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:622)
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:105)
... 16 common frames omitted
Caused by: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 00096AFE-A0E4-7243-ACC2-7CE5CBA58014
at com.aliyun.gateway.dingtalk.Client.modifyResponse(Client.java:42)
at com.aliyun.teaopenapi.Client.execute(Client.java:984)
at com.aliyun.dingtalkrobot_1_0.Client.orgGroupSendWithOptions(Client.java:541)
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:615)
... 17 common frames omitted
2026-02-13 09:13:37 [Thread-78] ERROR c.r.s.c.KingdeeWorkCenterDataController - 发送钉钉文件失败: 装二工段生产延期数据_20260213091337.xlsx
java.lang.RuntimeException: 发送企业机器人文件消息失败
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:108)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendMassageDelayDate(KingdeeWorkCenterDataController.java:672)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$FastClassBySpringCGLIB$$812397e7.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$EnhancerBySpringCGLIB$$8eb42999.sendMassageDelayDate(<generated>)
at com.ruoyi.web.controller.dingtalk.CallbackController.lambda$processEvent$1(CallbackController.java:269)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 089C935B-61ED-7EF3-B636-475DD1117BEF
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:622)
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:105)
... 16 common frames omitted
Caused by: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 089C935B-61ED-7EF3-B636-475DD1117BEF
at com.aliyun.gateway.dingtalk.Client.modifyResponse(Client.java:42)
at com.aliyun.teaopenapi.Client.execute(Client.java:984)
at com.aliyun.dingtalkrobot_1_0.Client.orgGroupSendWithOptions(Client.java:541)
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:615)
... 17 common frames omitted
2026-02-13 09:13:39 [Thread-78] ERROR c.r.s.c.KingdeeWorkCenterDataController - 发送钉钉文件失败: 委外中心生产延期数据_20260213091338.xlsx
java.lang.RuntimeException: 发送企业机器人文件消息失败
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:108)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendMassageDelayDate(KingdeeWorkCenterDataController.java:672)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$FastClassBySpringCGLIB$$812397e7.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$EnhancerBySpringCGLIB$$8eb42999.sendMassageDelayDate(<generated>)
at com.ruoyi.web.controller.dingtalk.CallbackController.lambda$processEvent$1(CallbackController.java:269)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: F0B57460-B5BE-7787-AF92-C0A6B29E15C9
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:622)
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:105)
... 16 common frames omitted
Caused by: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: F0B57460-B5BE-7787-AF92-C0A6B29E15C9
at com.aliyun.gateway.dingtalk.Client.modifyResponse(Client.java:42)
at com.aliyun.teaopenapi.Client.execute(Client.java:984)
at com.aliyun.dingtalkrobot_1_0.Client.orgGroupSendWithOptions(Client.java:541)
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:615)
... 17 common frames omitted
2026-02-13 09:13:40 [Thread-78] ERROR c.r.s.c.KingdeeWorkCenterDataController - 发送钉钉文件失败: 铆焊工段生产延期数据_20260213091339.xlsx
java.lang.RuntimeException: 发送企业机器人文件消息失败
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:108)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendMassageDelayDate(KingdeeWorkCenterDataController.java:672)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$FastClassBySpringCGLIB$$812397e7.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$EnhancerBySpringCGLIB$$8eb42999.sendMassageDelayDate(<generated>)
at com.ruoyi.web.controller.dingtalk.CallbackController.lambda$processEvent$1(CallbackController.java:269)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 86E9FA56-DB58-74D0-A80C-33D3AE558357
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:622)
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupFile(DingUtil.java:105)
... 16 common frames omitted
Caused by: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 86E9FA56-DB58-74D0-A80C-33D3AE558357
at com.aliyun.gateway.dingtalk.Client.modifyResponse(Client.java:42)
at com.aliyun.teaopenapi.Client.execute(Client.java:984)
at com.aliyun.dingtalkrobot_1_0.Client.orgGroupSendWithOptions(Client.java:541)
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:615)
... 17 common frames omitted
2026-02-13 09:13:40 [Thread-78] ERROR c.r.s.c.KingdeeWorkCenterDataController - 发送工段数据失败
java.lang.RuntimeException: 发送企业机器人Markdown消息失败
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupMarkdown(DingUtil.java:84)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendDingTalkMessage(KingdeeWorkCenterDataController.java:151)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController.sendMassageDelayDate(KingdeeWorkCenterDataController.java:693)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$FastClassBySpringCGLIB$$812397e7.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.ruoyi.system.controller.KingdeeWorkCenterDataController$$EnhancerBySpringCGLIB$$8eb42999.sendMassageDelayDate(<generated>)
at com.ruoyi.web.controller.dingtalk.CallbackController.lambda$processEvent$1(CallbackController.java:269)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 8F77C7D6-4E88-76CE-9E67-D9CF1007231B
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:622)
at com.ruoyi.common.dingding.DingUtil.sendOrgGroupMarkdown(DingUtil.java:81)
... 17 common frames omitted
Caused by: com.aliyun.tea.TeaException: code: 400, 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确; request id: 8F77C7D6-4E88-76CE-9E67-D9CF1007231B
at com.aliyun.gateway.dingtalk.Client.modifyResponse(Client.java:42)
at com.aliyun.teaopenapi.Client.execute(Client.java:984)
at com.aliyun.dingtalkrobot_1_0.Client.orgGroupSendWithOptions(Client.java:541)
at com.ruoyi.common.dingding.DingUtil.robotGroupSend(DingUtil.java:615)
... 18 common frames omitted
2026-04-12 09:48:37 [main] ERROR c.b.m.core.MybatisConfiguration - mapper[com.ruoyi.system.mapper.ProcessRouteMapper.existsByProcessNoAndMaterialCode] is ignored, because it exists, maybe from xml file

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,9 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.config.DingTalkProperties;
import com.ruoyi.common.dingding.DingCallbackCrypto;
import com.ruoyi.common.dingding.robot.RobotGroupService;
import com.ruoyi.common.dingding.util.DingUtil;
import com.ruoyi.common.dingding.webhook.WebhookClient;
import com.ruoyi.system.controller.KingdeeWorkCenterDataController;
import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.mapper.ProcessOrderProMapper;
@ -43,9 +45,9 @@ public class CallbackController {
private com.ruoyi.common.dingding.card.CardService cardService;
@Autowired
private com.ruoyi.common.dingding.robot.RobotGroupService robotGroupService; // 注入 RobotGroupService
private RobotGroupService robotGroupService; // 注入 RobotGroupService
@Autowired
private com.ruoyi.common.dingding.webhook.WebhookClient webhookClient; // 注入 WebhookClient
private WebhookClient webhookClient; // 注入 WebhookClient
@PostMapping("/receive")
public Map<String, String> callBack(@RequestBody(required = false) Map<String, Object> json) {
@ -110,6 +112,11 @@ public class CallbackController {
update.setUpdateBy("DingDing");
processOrderProMapper.updateById(update);
bizLogger.info("更新 ProcessOrderPro 状态成功: id={}, status={}", order.getId(), status);
// 如果审核通过假设 status 1L 表示通过给配置的人发消息
if (status != null && status == 1L) {
sendAuditCompleteNotification(order);
}
} else {
bizLogger.warn("未找到对应的 审核卡片Id: notificationCardId={}", outTrackId);
}
@ -118,6 +125,28 @@ public class CallbackController {
}
}
private void sendAuditCompleteNotification(ProcessOrderPro order) {
try {
String notifyUserIds = dingTalkProperties.getSyncK3NotifyUserIds();
if (notifyUserIds == null || notifyUserIds.trim().isEmpty()) {
bizLogger.warn("未配置审核完成通知人(syncK3NotifyUserIds)");
return;
}
String message = String.format("项目【%s】工艺审核已完成可以上传金蝶了", order.getProductionOrderNo());
String[] userIds = notifyUserIds.split(",");
for (String userId : userIds) {
if (userId != null && !userId.trim().isEmpty()) {
robotGroupService.sendMarkdownToUser(userId.trim(), "项目审核完成通知", message);
bizLogger.info("已发送审核完成通知给用户: {}", userId.trim());
}
}
} catch (Exception e) {
bizLogger.error("发送审核完成通知异常", e);
}
}
/**
* 处理具体事件逻辑
*/

View File

@ -49,8 +49,8 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
#url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
#url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
# 从库数据源
@ -58,16 +58,16 @@ spring:
lazy: true
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
#url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
sqlserver:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
url: jdbc:sqlserver://192.168.5.9:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
username: sa
password: 1a!
password: Hbyt@2026
hikari:
# 最大连接池数量
maxPoolSize: 20

View File

@ -52,8 +52,8 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
# url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
# 从库数据源
@ -61,16 +61,16 @@ spring:
lazy: true
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
#url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
sqlserver:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
url: jdbc:sqlserver://192.168.5.9:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
username: sa
password: 1a!
password: Hbyt@2026
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver

View File

@ -29,7 +29,7 @@ server:
port: 8033
servlet:
# 应用的访问路径
context-path: /dev-api
context-path: /
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1
@ -151,6 +151,10 @@ security:
- /system/orderPro/**
- /system/cost/**
- /ding/callback/**
- /system/material/receive
- /system/material/getObtainPartData2
- /system/operationPlanningMain/sync
- /system/operationPlanningMain/cascadeList
# MyBatisPlus配置
# https://baomidou.com/config/
@ -303,3 +307,5 @@ dingtalk:
callback-route-key: update_route_status2
notify-user-id: 0754423610937470
route-approval-id: 01201038164023402380
plan-user-id: 313514580637707245,192530665123549546
sync-k3-notify-user-ids: 313514580637707245,351225426738742993,192530665123549546

View File

@ -1,5 +1,5 @@
#??ID-PROD
X-KDApi-AcctID = 698ad0d6c0c3fe
X-KDApi-AcctID = 69d0e0f3a5801e
X-KDApi-UserName = Administrator
#??IDID
X-KDApi-AppID = 288012_Rc0C0zCG2lga0/Vs2Y4pzYSL6hQcWOko

View File

@ -284,6 +284,11 @@
<artifactId>app-stream-client</artifactId>
<version>1.3.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -57,6 +57,10 @@ public class DingTalkProperties {
* 工艺卡片模版id
*/
private String routeCardTemplateId;
/**
* 工艺计划接收人
*/
private String planUserId;
@ -64,11 +68,16 @@ public class DingTalkProperties {
* 接收工艺表格个人用户ID
*/
private String notifyUserId;
/**
/**
* 接收工艺审批用户ID
*/
private String routeApprovalId;
/**
* 项目审核完成后通知上传金蝶的用户ID多个用户ID用逗号分隔
*/
private String syncK3NotifyUserIds;
/**
* 回调路由 Key
*/

View File

@ -190,22 +190,12 @@ public class ExcelTemplateProc {
return;
}
// 添加调试信息显示静态数据处理
if (dataPrefix == null || dataPrefix.isEmpty()) {
System.out.println("开始处理静态数据行: " + row.getRowNum() + ", dataMap=" + dataMap.toString());
}
for (int i = firstCellNum; i < lastCellNum; i++) {
XSSFCell cell = row.getCell(i);
if (cell != null) {
// 添加调试信息检查静态数据处理
if (dataPrefix == null || dataPrefix.isEmpty()) {
String cellValue = cell.getStringCellValue();
if (cellValue != null && cellValue.contains("{{")) {
System.out.println("处理静态数据行,单元格" + i + ": " + cellValue);
}
}
fillInTemplateCellDataValue(cell, dataPrefix, dataMap);
}
}

View File

@ -0,0 +1,30 @@
package com.ruoyi.common.utils;
import com.ruoyi.common.core.domain.R;
import org.junit.Test;
public class FtpDownloadTest {
@Test
public void testDownload() {
String host = "192.168.5.18";
int port = 21; // 使用21端口测试下载根据你之前代F码的默认行为一般下载用21
String username = "admin";
String password = "hbyt2025";
// 你的实际测试路径
String remoteDir = "/2026/QB-26-072-XCL"; // 你想测试下载的 FTP 目录
String localDir = "D:\\file\\test_download"; // 本地存放目录
System.out.println("开始测试下载 FTP 目录: " + remoteDir);
// 调用 FtpUtil 下载文件
R<String> result = FtpUtil.downloadFtpDirectoryFiles(
host, username, password, port, remoteDir, localDir, null
);
System.out.println("下载结果 Code: " + result.getCode());
System.out.println("下载结果 Msg: " + result.getMsg());
System.out.println("下载结果 Data: " + result.getData());
}
}

View File

@ -43,6 +43,7 @@ public class SecurityConfig {
.antMatchers("/dev-api/system/material/list/").anonymous()
.antMatchers("/dev-api/system/dict/data/type/**").anonymous()
.antMatchers("/dev-api/index/list/").anonymous()
.antMatchers("/dev-api/system/material/").anonymous()
.antMatchers("/jmreport/**").anonymous()
.antMatchers(adminContextPath + "/assets/**"
, adminContextPath + "/login"

View File

@ -2260,6 +2260,9 @@ public class BomDetailsController extends BaseController {
List<BomDetails> list = new ArrayList<>();
if (rows != null) {
for (ElectricalMaterialBomVO bomVO : rows) {
if(bomVO.getRemark().equals("研发已领用")|| bomVO.getRemark().contains("领用")){
continue;
}
BomDetails bomDetails = new BomDetails();
bomDetails.setTotalWeight(bomVO.getProductionOrderNo());
bomDetails.setFNumber(bomVO.getParentDrawingNo());

View File

@ -27,6 +27,7 @@ import com.ruoyi.system.domain.bo.ImMaterialBo;
import com.ruoyi.system.domain.vo.ImMaterialVo;
import com.ruoyi.system.mapper.ImMaterialMapper;
import com.ruoyi.system.service.IImMaterialService;
import com.ruoyi.system.service.IPartCostService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
@ -47,6 +48,12 @@ import java.util.*;
import static com.ruoyi.common.core.mapper.BaseMapperPlus.log;
import cn.dev33.satoken.annotation.SaIgnore;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.system.domain.ImMaterial;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* 物料检索
*
@ -57,12 +64,56 @@ import static com.ruoyi.common.core.mapper.BaseMapperPlus.log;
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/material")
@Slf4j
public class ImMaterialController extends BaseController {
private final IImMaterialService iImMaterialService;
@Resource
private final ImMaterialMapper imMaterialMapper;
private final IPartCostService iPartCostService;
/**
* 金蝶物料保存 自动推送到此接口
* SaIgnore 注解用于开放此接口无需 token 验证
*/
@SaIgnore
@PostMapping(value = "/receive", consumes = "application/x-www-form-urlencoded;charset=UTF-8")
public String receiveMaterial(
@RequestParam String materialCode, @RequestParam String materialName, @RequestParam(required = false) String specification,
@RequestParam(required = false) String materialType, @RequestParam(required = false) String baseUnit,
@RequestParam(required = false) String createTime, @RequestParam(required = false) String enabled) {
try {
log.info("收到金蝶物料code={}, name={}, spec={}, type={}, unit={}, time={}, enabled={}",
materialCode, materialName, specification, materialType, baseUnit, createTime, enabled);
if (materialCode != null && !materialCode.isEmpty()) {
// TODO: 根据实际推送的数据进行处理
// 示例
// // 检查是否存在
// LambdaQueryWrapper<ImMaterial> wrapper = new LambdaQueryWrapper<>();
// wrapper.eq(ImMaterial::getCode, materialCode);
// ImMaterial material = imMaterialMapper.selectOne(wrapper);
// if (material == null) {
// // 新增
// material = new ImMaterial();
// material.setCode(materialCode);
// material.setName(materialName);
// // 设置其他必要字段...
// imMaterialMapper.insert(material);
// } else {
// // 更新
// material.setName(materialName);
// imMaterialMapper.updateById(material);
// }
}
return "success";
} catch (Exception e) {
log.error("处理金蝶推送物料失败", e);
return "fail";
}
}
/**
* 查询物料检索列表
*/
@ -434,4 +485,11 @@ public class ImMaterialController extends BaseController {
public TableDataInfo search(@RequestParam(value = "query", required = false) String query) {
return iImMaterialService.search(query);
}
@SaIgnore
@PostMapping("/getObtainPartData2")
public R<Void> getObtainPartData() {
log.info("接口进来了");
return toAjax(iPartCostService.getObtainPartData());
}
}

View File

@ -114,7 +114,6 @@ public class PartCostController extends BaseController {
@Log(title = "在金蝶获取成本价", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/getObtainPartData")
@XxlJob("getObtainPartData")
public R<Void> getObtainPartData() {
return toAjax(iPartCostService.getObtainPartData());
}

View File

@ -92,6 +92,12 @@ public class ProcessRouteController extends BaseController {
@Autowired
private ISysUserService userService;
@Autowired
private com.ruoyi.common.dingding.robot.RobotGroupService robotGroupService;
@Autowired
private com.ruoyi.common.dingding.media.MediaService mediaService;
private Long generateUniqueParentId(Long originalId) {
return originalId + 1000;
}
@ -407,6 +413,21 @@ public class ProcessRouteController extends BaseController {
return R.fail("项目 " + productionOrderNo + "已导入过工艺 ,请先清空再上传");
}
if (iProcessRouteService.saveData(list1, list)) {
// 保存临时文件用于发送给指定用户
File tempFile = null;
try {
String tempDirPath = "D:/file/";
File tempDir = new File(tempDirPath);
if (!tempDir.exists()) {
tempDir.mkdirs();
}
tempFile = new File(tempDirPath + System.currentTimeMillis() + "_" + originalFilename);
file.transferTo(tempFile);
} catch (Exception e) {
log.error("保存上传临时文件失败", e);
}
final File uploadFile = tempFile;
// 获取当前登录用户昵称需要在异步线程前获取
String currentUserNickName = "未知用户";
try {
@ -421,9 +442,39 @@ public class ProcessRouteController extends BaseController {
log.warn("获取当前登录用户昵称失败", e);
}
// 异步发送钉钉卡片
// 异步发送钉钉卡片和文件
String finalNickName = currentUserNickName;
CompletableFuture.runAsync(() -> {
try {
if (uploadFile != null && uploadFile.exists()) {
String planUserIds = dingTalkProperties.getPlanUserId();
if (StringUtils.isNotBlank(planUserIds)) {
String mediaId = mediaService.uploadMedia("file", uploadFile.getAbsolutePath());
// 支持多人推送假设多个 userId 以逗号分隔
String[] userIds = planUserIds.split(",");
for (String userId : userIds) {
String cleanUserId = userId.trim();
if (StringUtils.isNotBlank(cleanUserId)) {
try {
robotGroupService.sendFileToUser(cleanUserId, mediaId, originalFilename);
log.info("上传的工艺计划表已成功发送给用户: {}", cleanUserId);
} catch (Exception e) {
log.error("发送文件给用户 {} 异常", cleanUserId, e);
}
}
}
} else {
log.warn("未配置钉钉工艺计划接收人(planUserId),跳过发送文件");
}
}
} catch (Exception e) {
log.error("发送文件给工艺计划接收人异常", e);
} finally {
if (uploadFile != null && uploadFile.exists()) {
uploadFile.delete();
}
}
try {
ProcessOrderPro order = processOrderProService.selectByProjectNumber(productionOrderNo);
if (order != null) {
@ -443,13 +494,13 @@ public class ProcessRouteController extends BaseController {
DingUtil.createAndDeliverCardToUser(
accessToken,
dingTalkProperties.getRouteCardTemplateId(),
Collections.singletonList(dingTalkProperties.getNotifyUserId()),
Collections.singletonList(dingTalkProperties.getRouteApprovalId()),
outTrackId,
cardDataMap,
dingTalkProperties.getCallbackRouteKey()
);
// 统一存入带 userId 后缀的完整 outTrackId以便和钉钉回调保持一致
String fullOutTrackId = outTrackId + "_" + dingTalkProperties.getNotifyUserId();
String fullOutTrackId = outTrackId + "_" + dingTalkProperties.getRouteApprovalId();
// 更新数据库中的AuditCardId
ProcessOrderPro update = new ProcessOrderPro();
update.setId(order.getId());

View File

@ -0,0 +1,106 @@
package com.ruoyi.system.controller;
import java.util.List;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.vo.SfcOperationPlanningDetailVo;
import com.ruoyi.system.domain.bo.SfcOperationPlanningDetailBo;
import com.ruoyi.system.service.ISfcOperationPlanningDetailService;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 工序计划明细
*
* @author ruoyi
* @date 2026-04-10
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/operationPlanningDetail")
public class SfcOperationPlanningDetailController extends BaseController {
private final ISfcOperationPlanningDetailService iSfcOperationPlanningDetailService;
/**
* 查询工序计划明细列表
*/
@SaCheckPermission("system:operationPlanningDetail:list")
@GetMapping("/list")
public TableDataInfo<SfcOperationPlanningDetailVo> list(SfcOperationPlanningDetailBo bo, PageQuery pageQuery) {
return iSfcOperationPlanningDetailService.queryPageList(bo, pageQuery);
}
/**
* 导出工序计划明细列表
*/
@SaCheckPermission("system:operationPlanningDetail:export")
@Log(title = "工序计划明细", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SfcOperationPlanningDetailBo bo, HttpServletResponse response) {
List<SfcOperationPlanningDetailVo> list = iSfcOperationPlanningDetailService.queryList(bo);
ExcelUtil.exportExcel(list, "工序计划明细", SfcOperationPlanningDetailVo.class, response);
}
/**
* 获取工序计划明细详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:operationPlanningDetail:query")
@GetMapping("/{id}")
public R<SfcOperationPlanningDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(iSfcOperationPlanningDetailService.queryById(id));
}
/**
* 新增工序计划明细
*/
@SaCheckPermission("system:operationPlanningDetail:add")
@Log(title = "工序计划明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SfcOperationPlanningDetailBo bo) {
return toAjax(iSfcOperationPlanningDetailService.insertByBo(bo));
}
/**
* 修改工序计划明细
*/
@SaCheckPermission("system:operationPlanningDetail:edit")
@Log(title = "工序计划明细", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SfcOperationPlanningDetailBo bo) {
return toAjax(iSfcOperationPlanningDetailService.updateByBo(bo));
}
/**
* 删除工序计划明细
*
* @param ids 主键串
*/
@SaCheckPermission("system:operationPlanningDetail:remove")
@Log(title = "工序计划明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(iSfcOperationPlanningDetailService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@ -0,0 +1,133 @@
package com.ruoyi.system.controller;
import java.util.List;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.vo.SfcOperationPlanningMainVo;
import com.ruoyi.system.domain.bo.SfcOperationPlanningMainBo;
import com.ruoyi.system.service.ISfcOperationPlanningMainService;
import com.ruoyi.common.core.page.TableDataInfo;
import cn.dev33.satoken.annotation.SaIgnore;
import com.ruoyi.system.domain.dto.OperationPlanResultDTO;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
/**
* 工序计划主
*
* @author ruoyi
* @date 2026-04-10
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/operationPlanningMain")
public class SfcOperationPlanningMainController extends BaseController {
private final ISfcOperationPlanningMainService iSfcOperationPlanningMainService;
/**
* 同步工序计划数据
*/
@SaIgnore
//@SaCheckPermission("system:operationPlanningMain:sync")
@Log(title = "工序计划数据同步", businessType = BusinessType.INSERT)
@PostMapping("/sync")
public R<Void> syncData() {
return toAjax(iSfcOperationPlanningMainService.syncOperationPlanning());
}
/**
* 分页查询工序计划主及明细级联数据
*/
@SaIgnore
@GetMapping("/cascadeList")
public TableDataInfo<OperationPlanResultDTO> cascadeList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
return iSfcOperationPlanningMainService.queryCascadePageList(bo, pageQuery);
}
/**
* 查询工序计划主列表
*/
@SaCheckPermission("system:operationPlanningMain:list")
@GetMapping("/list")
public TableDataInfo<SfcOperationPlanningMainVo> list(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
return iSfcOperationPlanningMainService.queryPageList(bo, pageQuery);
}
/**
* 导出工序计划主列表
*/
@SaCheckPermission("system:operationPlanningMain:export")
@Log(title = "工序计划主", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SfcOperationPlanningMainBo bo, HttpServletResponse response) {
List<SfcOperationPlanningMainVo> list = iSfcOperationPlanningMainService.queryList(bo);
ExcelUtil.exportExcel(list, "工序计划主", SfcOperationPlanningMainVo.class, response);
}
/**
* 获取工序计划主详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:operationPlanningMain:query")
@GetMapping("/{id}")
public R<SfcOperationPlanningMainVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(iSfcOperationPlanningMainService.queryById(id));
}
/**
* 新增工序计划主
*/
@SaCheckPermission("system:operationPlanningMain:add")
@Log(title = "工序计划主", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SfcOperationPlanningMainBo bo) {
return toAjax(iSfcOperationPlanningMainService.insertByBo(bo));
}
/**
* 修改工序计划主
*/
@SaCheckPermission("system:operationPlanningMain:edit")
@Log(title = "工序计划主", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SfcOperationPlanningMainBo bo) {
return toAjax(iSfcOperationPlanningMainService.updateByBo(bo));
}
/**
* 删除工序计划主
*
* @param ids 主键串
*/
@SaCheckPermission("system:operationPlanningMain:remove")
@Log(title = "工序计划主", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(iSfcOperationPlanningMainService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@ -89,4 +89,9 @@ public class FigureSave extends BaseEntity {
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date drawTime;
/**
* 上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime;
}

View File

@ -0,0 +1,72 @@
package com.ruoyi.system.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data
public class ProcessRouteExcelDTO1 {
@ExcelProperty(index = 0)
private String materialCode;
@ExcelProperty(index = 1)
private String materialName;
@ExcelProperty(index = 2)
private String material;
@ExcelProperty(index = 3)
private Double discWeight;
@ExcelProperty(index = 4)
private String rawMaterialCode;
@ExcelProperty(index = 5)
private String rawMaterialName;
@ExcelProperty(index = 6)
private String bomMaterial;
@ExcelProperty(index = 7)
private Double bomDanZhong;
@ExcelProperty(index = 8)
private String discUsage;
@ExcelProperty(index = 9)
private String bomUnit;
@ExcelProperty(index = 10)
private Long processNo;
@ExcelProperty(index = 11)
private String workCenter;
@ExcelProperty(index = 12)
private String processName;
@ExcelProperty(index = 13)
private String processDescription;
@ExcelProperty(index = 14)
private String processControl;
@ExcelProperty(index = 15)
private Double activityDuration;
@ExcelProperty(index = 16)
private String activityUnit;
@ExcelProperty(index = 17)
private Double unitQuantity;
@ExcelProperty(index = 18)
private Long batchQuantity;
@ExcelProperty(index = 19)
private Date xuStartTime;
@ExcelProperty(index = 21)
private Date xuEndTime;
}

View File

@ -0,0 +1,115 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 工序计划明细对象 sfc_operation_planning_detail
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sfc_operation_planning_detail")
public class SfcOperationPlanningDetail extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 关联主表ID(sfc_operation_planning_main.id)
*/
private Long mainId;
/**
* 主表FID冗余
*/
private Long fid;
/**
* 单据体内码(K3明细内码)
*/
private Long entityEntryId;
/**
* 工序号
*/
private Long operNumber;
/**
* 工序数量
*/
private BigDecimal operQty;
/**
* 合格数量
*/
private BigDecimal qualifiedQty;
/**
* 转出数量
*/
private BigDecimal transOutQty;
/**
* 废品数量
*/
private BigDecimal unqualifiedQty;
/**
* 损耗数量
*/
private BigDecimal wastageQty;
/**
* 转入数量
*/
private BigDecimal transInQty;
/**
* 加工车间
*/
private String departmentName;
/**
* 工作中心
*/
private String workCenterName;
/**
* 作业
*/
private String processName;
/**
* 作业人员
*/
private String operator;
/**
* 状态
*/
private String operStatus;
/**
* 工序序列-计划开始时间
*/
private Date seqPlanStartTime;
/**
* 工序序列-计划完成时间
*/
private Date seqPlanFinishTime;
/**
* 工序控制码
*/
private String optCtrlCode;
/**
* 关键工序
*/
private String keyOper;
/**
* 活动一名称
*/
private String activityName;
/**
* 工序说明
*/
private String operDescription;
}

View File

@ -0,0 +1,66 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 工序计划主对象 sfc_operation_planning_main
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sfc_operation_planning_main")
public class SfcOperationPlanningMain extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* FID(K3单据内码)
*/
private Long fid;
/**
* 单据编号
*/
private String billNo;
/**
* 生产订单编号
*/
private String moNumber;
/**
* 生产令号
*/
private String hbytSclh;
/**
* 产品编码
*/
private String productCode;
/**
* 产品名称
*/
private String productName;
/**
* 规格型号
*/
private String proSpecification;
/**
* 计划开始时间
*/
private Date planStartTime;
/**
* 计划结束时间
*/
private Date planFinishTime;
}

View File

@ -1,5 +1,6 @@
package com.ruoyi.system.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
@ -26,7 +27,6 @@ public class FigureSaveBo extends BaseEntity {
/**
* 关联项目表
*/
@NotNull(message = "关联项目表不能为空", groups = { AddGroup.class, EditGroup.class })
private Long pid;
/**
@ -50,7 +50,6 @@ public class FigureSaveBo extends BaseEntity {
/**
* 图纸路径
*/
@NotBlank(message = "图纸路径不能为空", groups = { AddGroup.class, EditGroup.class })
private String drawPath;
/**
@ -108,4 +107,7 @@ public class FigureSaveBo extends BaseEntity {
*/
@NotNull(message = "出图时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date drawTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime;
}

View File

@ -0,0 +1,158 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 工序计划明细业务对象 sfc_operation_planning_detail
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SfcOperationPlanningDetailBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 关联主表ID(sfc_operation_planning_main.id)
*/
@NotNull(message = "关联主表ID(sfc_operation_planning_main.id)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long mainId;
/**
* 主表FID冗余
*/
@NotNull(message = "主表FID冗余不能为空", groups = { AddGroup.class, EditGroup.class })
private Long fid;
/**
* 单据体内码(K3明细内码)
*/
@NotNull(message = "单据体内码(K3明细内码)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long entityEntryId;
/**
* 工序号
*/
@NotNull(message = "工序号不能为空", groups = { AddGroup.class, EditGroup.class })
private Long operNumber;
/**
* 工序数量
*/
@NotNull(message = "工序数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal operQty;
/**
* 合格数量
*/
@NotNull(message = "合格数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal qualifiedQty;
/**
* 转出数量
*/
@NotNull(message = "转出数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal transOutQty;
/**
* 废品数量
*/
@NotNull(message = "废品数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal unqualifiedQty;
/**
* 损耗数量
*/
@NotNull(message = "损耗数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal wastageQty;
/**
* 转入数量
*/
@NotNull(message = "转入数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal transInQty;
/**
* 加工车间
*/
@NotBlank(message = "加工车间不能为空", groups = { AddGroup.class, EditGroup.class })
private String departmentName;
/**
* 工作中心
*/
@NotBlank(message = "工作中心不能为空", groups = { AddGroup.class, EditGroup.class })
private String workCenterName;
/**
* 作业
*/
@NotBlank(message = "作业不能为空", groups = { AddGroup.class, EditGroup.class })
private String processName;
/**
* 作业人员
*/
@NotBlank(message = "作业人员不能为空", groups = { AddGroup.class, EditGroup.class })
private String operator;
/**
* 状态
*/
@NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String operStatus;
/**
* 工序序列-计划开始时间
*/
@NotNull(message = "工序序列-计划开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date seqPlanStartTime;
/**
* 工序序列-计划完成时间
*/
@NotNull(message = "工序序列-计划完成时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date seqPlanFinishTime;
/**
* 工序控制码
*/
@NotBlank(message = "工序控制码不能为空", groups = { AddGroup.class, EditGroup.class })
private String optCtrlCode;
/**
* 关键工序
*/
@NotBlank(message = "关键工序不能为空", groups = { AddGroup.class, EditGroup.class })
private String keyOper;
/**
* 活动一名称
*/
@NotBlank(message = "活动一名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String activityName;
/**
* 工序说明
*/
@NotBlank(message = "工序说明不能为空", groups = { AddGroup.class, EditGroup.class })
private String operDescription;
}

View File

@ -0,0 +1,85 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 工序计划主业务对象 sfc_operation_planning_main
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SfcOperationPlanningMainBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* FID(K3单据内码)
*/
@NotNull(message = "FID(K3单据内码)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long fid;
/**
* 单据编号
*/
@NotBlank(message = "单据编号不能为空", groups = { AddGroup.class, EditGroup.class })
private String billNo;
/**
* 生产订单编号
*/
@NotBlank(message = "生产订单编号不能为空", groups = { AddGroup.class, EditGroup.class })
private String moNumber;
/**
* 生产令号
*/
@NotBlank(message = "生产令号不能为空", groups = { AddGroup.class, EditGroup.class })
private String hbytSclh;
/**
* 产品编码
*/
@NotBlank(message = "产品编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String productCode;
/**
* 产品名称
*/
@NotBlank(message = "产品名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String productName;
/**
* 规格型号
*/
@NotBlank(message = "规格型号不能为空", groups = { AddGroup.class, EditGroup.class })
private String proSpecification;
/**
* 计划开始时间
*/
@NotNull(message = "计划开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date planStartTime;
/**
* 计划结束时间
*/
@NotNull(message = "计划结束时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date planFinishTime;
}

View File

@ -0,0 +1,66 @@
package com.ruoyi.system.domain.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.util.List;
import java.util.Date;
@Data
public class OperationPlanResultDTO {
/**
* 生产订单编号
*/
private String moNumber;
/**
* 生产令号
*/
private String batchNo;
/**
* 产品编码
*/
private String productCode;
/**
* 产品名称
*/
private String productName;
/**
* 单据编号
*/
private String billNo;
/**
* 规格型号
*/
private String proSpecification;
/**
* 计划开始时间
*/
private String planStartTime;
/**
* 计划结束时间
*/
private String planFinishTime;
/**
* FID
*/
private Long fid;
/**
* 原生计划开始时间内部排序用不输出到JSON
*/
@JsonIgnore
private Date rawPlanStartTime;
/**
* 工序列表
*/
private List<ProcessDetailDTO> processList;
}

View File

@ -0,0 +1,176 @@
package com.ruoyi.system.domain.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class OperationPlanningDTO {
/**
* FID
*/
@JsonProperty("FID")
private Long fid;
/**
* 单据体内码
*/
@JsonProperty("FENTITY_FEntryID")
private Long fEntityFEntryId;
/**
* 生产令号
*/
@JsonProperty("F_HBYT_SCLH")
private String fHbytSclh;
/**
* 生产订单编号
*/
@JsonProperty("FMONumber")
private String fMoNumber;
/**
* 产品编码
*/
@JsonProperty("FProductId.FNumber")
private String fProductIdFNumber;
/**
* 产品名称
*/
@JsonProperty("FProductName")
private String fProductName;
/**
* 规格型号
*/
@JsonProperty("FProSpecification")
private String fProSpecification;
/**
* 单据编号
*/
@JsonProperty("FBillNo")
private String fBillNo;
/**
* 工序号
*/
@JsonProperty("FOperNumber")
private Integer fOperNumber;
/**
* 工序数量
*/
@JsonProperty("FOperQty")
private Double fOperQty;
/**
* 合格数量
*/
@JsonProperty("FQualifiedQty")
private Double fQualifiedQty;
/**
* 转出数量
*/
@JsonProperty("FTransOutQty")
private Double fTransOutQty;
/**
* 废品数量
*/
@JsonProperty("FUnqualifiedQty")
private Double fUnqualifiedQty;
/**
* 损耗数量
*/
@JsonProperty("FWastageQty")
private Double fWastageQty;
/**
* 转入数量
*/
@JsonProperty("FTransInQty")
private Double fTransInQty;
/**
* 加工车间
*/
@JsonProperty("FDepartmentId.FName")
private String fDepartmentIdFName;
/**
* 工作中心
*/
@JsonProperty("FWorkCenterId.FName")
private String fWorkCenterIdFName;
/**
* 作业
*/
@JsonProperty("FProcessId.FName")
private String fProcessIdFName;
/**
* 作业人员
*/
@JsonProperty("F_HBYT_ZYRY")
private String fHbytZyry;
/**
* 状态
*/
@JsonProperty("FOperStatus")
private String fOperStatus;
/**
* 工序序列-计划开始时间
*/
@JsonProperty("FSeqPlanStartTime")
private String fSeqPlanStartTime;
/**
* 工序序列-计划完成时间
*/
@JsonProperty("FSeqPlanFinishTime")
private String fSeqPlanFinishTime;
/**
* 计划开始时间
*/
@JsonProperty("FOperPlanStartTime")
private String fOperPlanStartTime;
/**
* 计划结束时间
*/
@JsonProperty("FOperPlanFinishTime")
private String fOperPlanFinishTime;
/**
* 工序控制码
*/
@JsonProperty("FOptCtrlCodeId.FName")
private String fOptCtrlCodeIdFName;
/**
* 关键工序
*/
@JsonProperty("FKeyOper")
private String fKeyOper;
/**
* 活动一名称
*/
@JsonProperty("FActivity1Name")
private String fActivity1Name;
/**
* 工序说明
*/
@JsonProperty("FOperDescription")
private String fOperDescription;
}

View File

@ -0,0 +1,101 @@
package com.ruoyi.system.domain.dto;
import lombok.Data;
@Data
public class ProcessDetailDTO {
/**
* 工序号
*/
private Integer operNo;
/**
* 作业/工序名称
*/
private String processName;
/**
* 工作中心
*/
private String workCenter;
/**
* 计划/工序数量
*/
private Double planQty;
/**
* 转入数量
*/
private Double inQty;
/**
* 合格数量
*/
private Double okQty;
/**
* 废品数量
*/
private Double ngQty;
/**
* 状态
*/
private String status;
/**
* 转出数量
*/
private Double transOutQty;
/**
* 损耗数量
*/
private Double wastageQty;
/**
* 加工车间
*/
private String department;
/**
* 作业人员
*/
private String operator;
/**
* 工序序列-计划开始时间
*/
private String seqPlanStartTime;
/**
* 工序序列-计划完成时间
*/
private String seqPlanFinishTime;
/**
* 工序控制码
*/
private String optCtrlCode;
/**
* 关键工序
*/
private String keyOper;
/**
* 活动一名称
*/
private String activity1Name;
/**
* 工序说明
*/
private String operDescription;
/**
* 单据体内码
*/
private Long entityEntryId;
}

View File

@ -2,6 +2,7 @@ package com.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@ -104,4 +105,7 @@ public class FigureSaveVo {
*/
@ExcelProperty(value = "出图时间")
private Date drawTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime;
}

View File

@ -0,0 +1,158 @@
package com.ruoyi.system.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.annotation.ExcelDictFormat;
import com.ruoyi.common.convert.ExcelDictConvert;
import lombok.Data;
/**
* 工序计划明细视图对象 sfc_operation_planning_detail
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@ExcelIgnoreUnannotated
public class SfcOperationPlanningDetailVo {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 关联主表ID(sfc_operation_planning_main.id)
*/
@ExcelProperty(value = "关联主表ID(sfc_operation_planning_main.id)")
private Long mainId;
/**
* 主表FID冗余
*/
@ExcelProperty(value = "主表FID冗余")
private Long fid;
/**
* 单据体内码(K3明细内码)
*/
@ExcelProperty(value = "单据体内码(K3明细内码)")
private Long entityEntryId;
/**
* 工序号
*/
@ExcelProperty(value = "工序号")
private Long operNumber;
/**
* 工序数量
*/
@ExcelProperty(value = "工序数量")
private BigDecimal operQty;
/**
* 合格数量
*/
@ExcelProperty(value = "合格数量")
private BigDecimal qualifiedQty;
/**
* 转出数量
*/
@ExcelProperty(value = "转出数量")
private BigDecimal transOutQty;
/**
* 废品数量
*/
@ExcelProperty(value = "废品数量")
private BigDecimal unqualifiedQty;
/**
* 损耗数量
*/
@ExcelProperty(value = "损耗数量")
private BigDecimal wastageQty;
/**
* 转入数量
*/
@ExcelProperty(value = "转入数量")
private BigDecimal transInQty;
/**
* 加工车间
*/
@ExcelProperty(value = "加工车间")
private String departmentName;
/**
* 工作中心
*/
@ExcelProperty(value = "工作中心")
private String workCenterName;
/**
* 作业
*/
@ExcelProperty(value = "作业")
private String processName;
/**
* 作业人员
*/
@ExcelProperty(value = "作业人员")
private String operator;
/**
* 状态
*/
@ExcelProperty(value = "状态")
private String operStatus;
/**
* 工序序列-计划开始时间
*/
@ExcelProperty(value = "工序序列-计划开始时间")
private Date seqPlanStartTime;
/**
* 工序序列-计划完成时间
*/
@ExcelProperty(value = "工序序列-计划完成时间")
private Date seqPlanFinishTime;
/**
* 工序控制码
*/
@ExcelProperty(value = "工序控制码")
private String optCtrlCode;
/**
* 关键工序
*/
@ExcelProperty(value = "关键工序")
private String keyOper;
/**
* 活动一名称
*/
@ExcelProperty(value = "活动一名称")
private String activityName;
/**
* 工序说明
*/
@ExcelProperty(value = "工序说明")
private String operDescription;
}

View File

@ -0,0 +1,85 @@
package com.ruoyi.system.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.annotation.ExcelDictFormat;
import com.ruoyi.common.convert.ExcelDictConvert;
import lombok.Data;
/**
* 工序计划主视图对象 sfc_operation_planning_main
*
* @author ruoyi
* @date 2026-04-10
*/
@Data
@ExcelIgnoreUnannotated
public class SfcOperationPlanningMainVo {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* FID(K3单据内码)
*/
@ExcelProperty(value = "FID(K3单据内码)")
private Long fid;
/**
* 单据编号
*/
@ExcelProperty(value = "单据编号")
private String billNo;
/**
* 生产订单编号
*/
@ExcelProperty(value = "生产订单编号")
private String moNumber;
/**
* 生产令号
*/
@ExcelProperty(value = "生产令号")
private String hbytSclh;
/**
* 产品编码
*/
@ExcelProperty(value = "产品编码")
private String productCode;
/**
* 产品名称
*/
@ExcelProperty(value = "产品名称")
private String productName;
/**
* 规格型号
*/
@ExcelProperty(value = "规格型号")
private String proSpecification;
/**
* 计划开始时间
*/
@ExcelProperty(value = "计划开始时间")
private Date planStartTime;
/**
* 计划结束时间
*/
@ExcelProperty(value = "计划结束时间")
private Date planFinishTime;
}

View File

@ -0,0 +1,47 @@
package com.ruoyi.system.job;
import com.ruoyi.system.service.ISfcOperationPlanningMainService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 工序计划定时任务
*
* @author ruoyi
* @date 2026-04-10
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SfcOperationPlanningJob {
private final ISfcOperationPlanningMainService iSfcOperationPlanningMainService;
/**
* 定时同步K3工序计划数据
* XXL-JOB 定时任务入口
*/
@XxlJob("syncOperationPlanningJobHandler")
public void syncOperationPlanningJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, 开始同步K3工序计划数据...");
long startTime = System.currentTimeMillis();
try {
Boolean result = iSfcOperationPlanningMainService.syncOperationPlanning();
if (result) {
XxlJobHelper.log("XXL-JOB, 同步K3工序计划数据成功耗时: {}ms", (System.currentTimeMillis() - startTime));
XxlJobHelper.handleSuccess("同步成功");
} else {
XxlJobHelper.log("XXL-JOB, 同步K3工序计划数据失败");
XxlJobHelper.handleFail("同步失败,请查看系统日志");
}
} catch (Exception e) {
log.error("同步K3工序计划数据异常", e);
XxlJobHelper.log("XXL-JOB, 同步K3工序计划数据异常: {}", e.getMessage());
XxlJobHelper.handleFail("同步异常: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,15 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.SfcOperationPlanningDetail;
import com.ruoyi.system.domain.vo.SfcOperationPlanningDetailVo;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
/**
* 工序计划明细Mapper接口
*
* @author ruoyi
* @date 2026-04-10
*/
public interface SfcOperationPlanningDetailMapper extends BaseMapperPlus<SfcOperationPlanningDetailMapper, SfcOperationPlanningDetail, SfcOperationPlanningDetailVo> {
}

View File

@ -0,0 +1,15 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.SfcOperationPlanningMain;
import com.ruoyi.system.domain.vo.SfcOperationPlanningMainVo;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
/**
* 工序计划主Mapper接口
*
* @author ruoyi
* @date 2026-04-10
*/
public interface SfcOperationPlanningMainMapper extends BaseMapperPlus<SfcOperationPlanningMainMapper, SfcOperationPlanningMain, SfcOperationPlanningMainVo> {
}

View File

@ -3659,4 +3659,147 @@ public class JdUtil {
return plannedProcessList; // 返回结果
}
/**
* SFC_OperationPlanning查询
* @return
*/
public static List<OperationPlanningDTO> getOperationPlanningList() {
K3CloudApi client = new K3CloudApi();
// 请求参数要求为json字符串
JsonObject json = new JsonObject();
json.addProperty("FormId", "SFC_OperationPlanning");
json.addProperty("FieldKeys",
"FID,FENTITY_FEntryID,F_HBYT_SCLH,FMONumber,FProductId.FNumber,FProductName,FProSpecification,FBillNo,FOperNumber,FOperQty,FQualifiedQty,FTransOutQty,FUnqualifiedQty,FWastageQty,FTransInQty,FDepartmentId.FName,FWorkCenterId.FName,FProcessId.FName,F_HBYT_ZYRY,FOperStatus,FSeqPlanStartTime,FSeqPlanFinishTime,FOperPlanStartTime,FOperPlanFinishTime,FOptCtrlCodeId.FName,FKeyOper,FActivity1Name,FOperDescription");
// 创建过滤条件
JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject();
/* filterObject.addProperty("FieldName", "FOperStatus");
filterObject.addProperty("Compare", "105");
filterObject.addProperty("Value", "4");
filterObject.addProperty("Left", "");
filterObject.addProperty("Right", "");
filterObject.addProperty("Logic", 0);
filterString.add(filterObject);*/
json.add("FilterString", filterString);
json.addProperty("OrderString", "");
json.addProperty("TopRowCount", 0);
json.addProperty("StartRow", 0);
json.addProperty("Limit", 10000);
json.addProperty("SubSystemId", "");
String jsonData = json.toString();
List<OperationPlanningDTO> operationPlanningList = null;
try {
// 调用接口
String resultJson = String.valueOf(client.billQuery(jsonData));
JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class);
// 使用 ObjectMapper JsonArray 转换为 List<OperationPlanningDTO>
ObjectMapper objectMapper = new ObjectMapper();
operationPlanningList = objectMapper.readValue(jsonArray.toString(),
new TypeReference<List<OperationPlanningDTO>>() {
});
} catch (Exception e) {
e.printStackTrace(); // 输出异常日志
}
return operationPlanningList; // 返回结果
}
/**
* 将查询出的工序计划数据整理成层级结构的JSON格式
* @return
*/
public static List<OperationPlanResultDTO> getStructuredOperationPlanningList() {
List<OperationPlanningDTO> rawList = getOperationPlanningList();
if (rawList == null || rawList.isEmpty()) {
return new ArrayList<>();
}
// 按生产订单编号FMONumber分组
Map<String, List<OperationPlanningDTO>> groupedMap = rawList.stream()
.filter(item -> item.getFMoNumber() != null)
.collect(java.util.stream.Collectors.groupingBy(OperationPlanningDTO::getFMoNumber));
List<OperationPlanResultDTO> resultList = new ArrayList<>();
for (Map.Entry<String, List<OperationPlanningDTO>> entry : groupedMap.entrySet()) {
String moNumber = entry.getKey();
List<OperationPlanningDTO> items = entry.getValue();
OperationPlanningDTO firstItem = items.get(0);
OperationPlanResultDTO resultDTO = new OperationPlanResultDTO();
resultDTO.setMoNumber(moNumber);
resultDTO.setBatchNo(firstItem.getFHbytSclh()); // 生产令号
resultDTO.setProductCode(firstItem.getFProductIdFNumber());
resultDTO.setProductName(firstItem.getFProductName());
resultDTO.setBillNo(firstItem.getFBillNo());
resultDTO.setProSpecification(firstItem.getFProSpecification());
resultDTO.setPlanStartTime(firstItem.getFOperPlanStartTime());
resultDTO.setPlanFinishTime(firstItem.getFOperPlanFinishTime());
resultDTO.setFid(firstItem.getFid());
List<ProcessDetailDTO> processList = new ArrayList<>();
for (OperationPlanningDTO item : items) {
ProcessDetailDTO detail = new ProcessDetailDTO();
detail.setOperNo(item.getFOperNumber());
detail.setProcessName(item.getFProcessIdFName());
detail.setWorkCenter(item.getFWorkCenterIdFName());
detail.setPlanQty(item.getFOperQty());
detail.setInQty(item.getFTransInQty());
detail.setOkQty(item.getFQualifiedQty());
detail.setNgQty(item.getFUnqualifiedQty());
// 补充其余遗漏数据
detail.setTransOutQty(item.getFTransOutQty());
detail.setWastageQty(item.getFWastageQty());
detail.setDepartment(item.getFDepartmentIdFName());
detail.setOperator(item.getFHbytZyry());
detail.setSeqPlanStartTime(item.getFSeqPlanStartTime());
detail.setSeqPlanFinishTime(item.getFSeqPlanFinishTime());
detail.setOptCtrlCode(item.getFOptCtrlCodeIdFName());
detail.setKeyOper(item.getFKeyOper());
detail.setActivity1Name(item.getFActivity1Name());
detail.setOperDescription(item.getFOperDescription());
detail.setEntityEntryId(item.getFEntityFEntryId());
// 状态映射K3里4通常表示已完工
String statusStr = item.getFOperStatus();
if ("0".equals(statusStr)) {
detail.setStatus("创建");
}
else if ("1".equals(statusStr)) {
detail.setStatus("计划");
} else if ("2".equals(statusStr)) {
detail.setStatus("计划确认");
} else if ("3".equals(statusStr)) {
detail.setStatus("下达");
} else if ("4".equals(statusStr)) {
detail.setStatus("开工");
} else if ("5".equals(statusStr)) {
detail.setStatus("完工");
} else if ("6".equals(statusStr)) {
detail.setStatus("关闭");
} else {
detail.setStatus(statusStr);
}
processList.add(detail);
}
// 按工序号排序
processList.sort(Comparator.comparing(ProcessDetailDTO::getOperNo, Comparator.nullsLast(Integer::compareTo)));
resultDTO.setProcessList(processList);
resultList.add(resultDTO);
}
return resultList;
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.*;
@ -72,7 +73,7 @@ public class PDFGenerator {
nextX += colWidths[i];
}
return contentStream; // 返回可能更新后的contentStream
}
@ -93,9 +94,8 @@ public class PDFGenerator {
return LosslessFactory.createFromImage(document, bufferedImage);
}
public static String writeToPdf(List<CombinedDTO> combinedVoList, String rooteProdet) {
public static String writeToPdf(List<CombinedDTO> combinedVoList, String rooteProdet) throws IOException {
String fontPath = "C:\\Users\\Administrator\\Desktop\\arial unicode ms.ttf";
List<String> pdfPaths = new ArrayList<>();
String directoryPath = "D:\\上传BOM\\" + rooteProdet;
// 检查目录是否存在如果不存在则创建
File directory = new File(directoryPath);
@ -125,216 +125,47 @@ public class PDFGenerator {
throw new RuntimeException("清理目录失败: " + directoryPath + ", " + e.getMessage(), e);
}
}
for (CombinedDTO combinedVo : combinedVoList) {
if (!combinedVoList.isEmpty()) {
// 获取物料编码
String materialCode1 = combinedVo.getMaterialCode();
String fProcessName;
String productionOrderNumber;
Date starttime;
Date endtime;
String materialCode;
Long fmoQty;
String fDepartmentName;
String OrderNumber = "";
// 获取第一序的工序名称
List<PlannedProcessVo> processes = combinedVo.getProcesses();
if (!processes.isEmpty()) {
fProcessName = processes.get(0).getFProcessName();
OrderNumber = combinedVo.getOrderNumber();
productionOrderNumber = String.valueOf(combinedVo.getProcesses().get(0).getFBillNo());
starttime = combinedVo.getProcesses().get(0).getFPlanStartTime();
endtime = combinedVo.getProcesses().get(0).getFPlanFinishTime();
materialCode = String.valueOf(combinedVo.getMaterialUsageDTOList().get(0).getBillNumber());
fmoQty = combinedVo.getProcesses().get(0).getFMOQty();
fDepartmentName = combinedVo.getProcesses().get(0).getFWorkCenterName();
// 其他代码...
} else {
// 处理列表为空的情况
fProcessName = "A";
productionOrderNumber = "A";
starttime = new Date();
endtime = new Date();
materialCode = "0";
fmoQty = 0L;
fDepartmentName = "";
Map<String, List<CombinedDTO>> groupMap = new LinkedHashMap<>();
if (combinedVoList != null) {
for (CombinedDTO dto : combinedVoList) {
if (dto == null) {
continue;
}
String prefix = getMaterialPrefix(dto.getMaterialCode());
groupMap.computeIfAbsent(prefix, k -> new ArrayList<>()).add(dto);
}
}
String pdfFileName = fDepartmentName + "_" + materialCode1.replace("/", "_") + "_独立" + ".pdf";
for (Map.Entry<String, List<CombinedDTO>> entry : groupMap.entrySet()) {
List<CombinedDTO> groupList = entry.getValue();
if (groupList == null || groupList.isEmpty()) {
continue;
}
if (groupList.size() == 1) {
createSinglePdf(groupList.get(0), rooteProdet, directoryPath, fontPath);
continue;
}
// 1. 获取文件夹名
String departmentFolderName = fDepartmentName;
if (departmentFolderName == null || departmentFolderName.trim().isEmpty()) {
// 兜底从文件名取前四个字或以'_'分割第一个元素
String[] parts = pdfFileName.split("_");
departmentFolderName = parts.length > 0 ? parts[0]
: pdfFileName.substring(0, Math.min(4, pdfFileName.length()));
if (groupList.size() == 2) {
generateTwoInOnePage(groupList.get(0), groupList.get(1), rooteProdet, directoryPath, fontPath, entry.getKey());
continue;
}
List<String> groupPdfPaths = new ArrayList<>();
for (CombinedDTO combinedVo : groupList) {
String pdfPath = createSinglePdf(combinedVo, rooteProdet, directoryPath, fontPath);
if (pdfPath != null && !pdfPath.trim().isEmpty()) {
groupPdfPaths.add(pdfPath);
}
}
// 2. 创建文件夹
File departmentFolder = new File(directoryPath, departmentFolderName);
if (!departmentFolder.exists()) {
departmentFolder.mkdirs();
}
// 获取当前时间的毫秒时间戳
long timestamp = System.currentTimeMillis();
String pdfPath = departmentFolder.getAbsolutePath() + "\\" + fDepartmentName + "_" + materialCode1.replace("/", "_") + "_" + productionOrderNumber + ".pdf";
PDDocument document = null;
try {
// 创建 PDF 文件并写入内容
document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDType0Font font = PDType0Font.load(document, new File(fontPath));
try {
font = PDType0Font.load(document, new File(fontPath));
} catch (IllegalArgumentException e) {
System.err.println("Font file not found: " + fontPath);
throw new RuntimeException("Font file not found", e);
}
PDPageContentStream contentStream = new PDPageContentStream(document, page);
float margin = 15;
float yStart = 740;
float rowHeight = 20f;
float pageHeight = page.getMediaBox().getHeight();
float tableWidth = page.getMediaBox().getWidth() - 2 * margin;
// 添加标题和右上角的二维码
contentStream.beginText();
contentStream.setFont(font, 14);
contentStream.newLineAtOffset(220, 780);
contentStream.showText("工序计划单");
contentStream.endText();
PDImageXObject qrCode1 = generateQRCode(document, productionOrderNumber, 90, 90);
contentStream.drawImage(qrCode1, page.getMediaBox().getWidth() - 110, 710);
SimpleDateFormat formatte11r = new SimpleDateFormat("yyyy-MM-dd");
// 绘制生产订单信息表格
float[] colWidths = { 60, 95, 110, 110, 60, 60, 55 };
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths,
new String[] { "生产令号", rooteProdet,
"开始时间", formatte11r.format(starttime), "结束时间", formatte11r.format(endtime) },
document, page, rowHeight, pageHeight);
yStart -= rowHeight;
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths,
new String[] { "单据编号", "生产订单编号", "产品名称", "产品编码", "数量", "生产车间", "次数" }, document, page,
rowHeight, pageHeight);
yStart -= rowHeight;
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths, new String[] {
productionOrderNumber, combinedVo.getOrderNumber(), combinedVo.getMaterialName(),
combinedVo.getMaterialCode(),
String.valueOf(fmoQty), fDepartmentName, "1", "" }, document, page, rowHeight, pageHeight);
// 绘制工序表格
float processTableStartY = yStart - 2 * rowHeight - 10;
float[] processColWidths = { 35, 78, 35, 55, 45, 170, 55, 60, 60 };
yStart = processTableStartY;
// 使用支持换行的方法绘制表头
Object[] result = drawTableRowWithWrap(contentStream, font, margin, yStart, tableWidth, processColWidths, new String[] {
"工序号", "工序名称", "数量", "工作中心", "工时(分)", "工序说明", "控制码", "开始时间", "结束时间" }, document, page,
rowHeight, pageHeight);
contentStream = (PDPageContentStream) result[0];
yStart = (Float) result[1];
for (PlannedProcessVo process : combinedVo.getProcesses()) {
SimpleDateFormat formatte1r = new SimpleDateFormat("yyyy-MM-dd");
// 使用支持换行的方法绘制数据行特别是工序说明字段
Object[] processResult = drawTableRowWithWrap(contentStream, font, margin, yStart, tableWidth, processColWidths, new String[] {
String.valueOf(process.getFOperNumber()), process.getFProcessName(),
String.valueOf(process.getFOperQty()),
process.getFWorkCenterName(), String.format("%.2f", process.getFActivity1BaseQty()),
process.getFOperDescription(),
process.getFOptCtrlCodeIFName(),
process.getFSeqPlanStartTime() != null
? formatte1r.format(process.getFSeqPlanStartTime())
: "",
process.getFSeqPlanFinishTime() != null
? (formatte1r.format(process.getFSeqPlanFinishTime()))
: ""
}, document, page, rowHeight, pageHeight);
contentStream = (PDPageContentStream) processResult[0];
yStart = (Float) processResult[1];
}
// 添加生产用料清单标题
yStart = yStart - 2 * rowHeight - 40;
contentStream.beginText();
contentStream.setFont(font, 12);
contentStream.newLineAtOffset(margin, yStart);
contentStream.showText("生产用料清单:");
contentStream.showText(materialCode);
contentStream.endText();
// 在用料清单标题右侧添加二维码与右上角二维码对齐
PDImageXObject qrCode2 = generateQRCode(document, materialCode, 90, 90);
contentStream.drawImage(qrCode2, page.getMediaBox().getWidth() - 110, yStart - 31);
yStart -= rowHeight;
// 用料清单表头
float[] materialColWidths = { 25, 130, 110, 50, 35, 40, 40, 80, 50 };
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths, new String[] {
"序号", "物料编码", "物料名称", "规格型号", "单位", "应发数", "已领数", "仓库", "备注"
}, document, page, rowHeight, pageHeight);
List<MaterialUsageDTO> materialUsageDTOList = combinedVo.getMaterialUsageDTOList();
if (materialUsageDTOList != null && !materialUsageDTOList.isEmpty()) {
for (int i = 0; i < materialUsageDTOList.size(); i++) {
yStart -= rowHeight;
// 如果空间不足则新建页面
if (yStart - rowHeight < margin) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
yStart = pageHeight - margin;
// 绘制用料清单表头
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths,
new String[] {
"序号", "物料编码", "物料名称", "规格型号", "单位", "应发数", "已领数", "仓库", "备注"
}, document, page, rowHeight, pageHeight);
yStart -= rowHeight;
}
MaterialUsageDTO material = materialUsageDTOList.get(i);
SimpleDateFormat formatte1r = new SimpleDateFormat("yyyy-MM-dd");
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths,
new String[] {
String.valueOf(i + 1), material.getMaterialCode(),
material.getMaterialName(),
material.getSpecification(), material.getUnit(),
String.valueOf(material.getRequiredQty()),
String.valueOf(material.getPickedQty()),
material.getStockName(),
material.getRemarks() != null ? material.getRemarks() : ""
}, document, page, rowHeight, pageHeight);
}
}
contentStream.close();
document.save(pdfPath);
pdfPaths.add(pdfPath);
} catch (IOException | WriterException e) {
e.printStackTrace();
} finally {
if (document != null) {
try {
document.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (groupPdfPaths.size() > 1) {
String mergedPath = buildMergedPdfPath(groupList.get(0), directoryPath, entry.getKey());
Files.move(Paths.get(groupPdfPaths.get(0)), Paths.get(mergedPath), StandardCopyOption.REPLACE_EXISTING);
for (int i = 1; i < groupPdfPaths.size(); i++) {
String p = groupPdfPaths.get(i);
mergePdfToExisting(p, mergedPath);
Files.deleteIfExists(Paths.get(p));
}
}
}
@ -350,6 +181,502 @@ public class PDFGenerator {
}
private static String getMaterialPrefix(String materialCode) {
if (materialCode == null) {
return "";
}
int index = materialCode.indexOf("-");
if (index > 0) {
return materialCode.substring(0, index);
}
return materialCode;
}
private static String getDepartmentName(CombinedDTO combinedVo) {
List<PlannedProcessVo> processes = combinedVo.getProcesses();
if (processes != null && !processes.isEmpty()) {
String d = processes.get(0).getFWorkCenterName();
return d != null ? d : "";
}
return "";
}
private static String buildMergedPdfPath(CombinedDTO anyVo, String directoryPath, String prefix) {
String dept = getDepartmentName(anyVo);
String safePrefix = prefix != null ? prefix.replace("/", "_") : "";
String fileName = (safePrefix.isEmpty() ? ("UNKNOWN_" + System.currentTimeMillis()) : safePrefix) + ".pdf";
String folderName = dept;
if (folderName == null || folderName.trim().isEmpty()) {
String[] parts = fileName.split("_");
folderName = parts.length > 0 ? parts[0] : "merge";
}
File departmentFolder = new File(directoryPath, folderName);
if (!departmentFolder.exists()) {
departmentFolder.mkdirs();
}
return departmentFolder.getAbsolutePath() + "\\" + fileName;
}
private static String generateTwoInOnePage(CombinedDTO dto1, CombinedDTO dto2, String rooteProdet, String directoryPath, String fontPath, String prefix) {
if (dto1 == null || dto2 == null) {
return null;
}
String outputPath = buildMergedPdfPath(dto1, directoryPath, prefix);
PDDocument document = null;
PDPageContentStream contentStream = null;
try {
document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDType0Font font = PDType0Font.load(document, new File(fontPath));
contentStream = new PDPageContentStream(document, page);
float topStart = 780f;
float bottomStart = 380f;
drawPlanIntoArea(dto1, document, page, contentStream, font, rooteProdet, topStart, 400f);
drawPlanIntoArea(dto2, document, page, contentStream, font, rooteProdet, bottomStart, 15f);
contentStream.close();
contentStream = null;
document.save(outputPath);
return outputPath;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (contentStream != null) {
try {
contentStream.close();
} catch (IOException ignored) {
}
}
if (document != null) {
try {
document.close();
} catch (IOException ignored) {
}
}
}
}
private static void drawPlanIntoArea(CombinedDTO combinedVo, PDDocument document, PDPage page, PDPageContentStream contentStream,
PDType0Font font, String rooteProdet, float titleY, float minY) throws IOException, WriterException {
float margin = 15f;
float rowHeight = 14f;
float fontSize = 8f;
float pageWidth = page.getMediaBox().getWidth();
float qrSize = 90f;
float qrGap = 5f;
float tableWidth = pageWidth - 2 * margin - (qrSize + qrGap);
float qrX = pageWidth - margin - qrSize;
List<PlannedProcessVo> processes = combinedVo.getProcesses();
String productionOrderNumber;
Date starttime;
Date endtime;
Long fmoQty;
String fDepartmentName;
if (processes != null && !processes.isEmpty()) {
productionOrderNumber = String.valueOf(processes.get(0).getFBillNo());
starttime = processes.get(0).getFPlanStartTime();
endtime = processes.get(0).getFPlanFinishTime();
fmoQty = processes.get(0).getFMOQty();
fDepartmentName = processes.get(0).getFWorkCenterName();
} else {
productionOrderNumber = "A";
starttime = new Date();
endtime = new Date();
fmoQty = 0L;
fDepartmentName = "";
}
String materialCodeQr;
if (combinedVo.getMaterialUsageDTOList() != null && !combinedVo.getMaterialUsageDTOList().isEmpty()) {
materialCodeQr = String.valueOf(combinedVo.getMaterialUsageDTOList().get(0).getBillNumber());
} else {
materialCodeQr = "0";
}
if (titleY - qrSize >= minY) {
PDImageXObject qrCode1 = generateQRCode(document, productionOrderNumber, (int) qrSize, (int) qrSize);
contentStream.drawImage(qrCode1, qrX, titleY - qrSize + 5);
}
SimpleDateFormat formatte11r = new SimpleDateFormat("yyyy-MM-dd");
float[] colWidths = scaleWidths(new float[] { 60, 95, 110, 110, 60, 60, 55 }, tableWidth);
float yStart = titleY - 5;
Object[] r1 = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart, tableWidth, colWidths,
new String[] { "生产令号", rooteProdet, "开始时间", formatte11r.format(starttime), "结束时间", formatte11r.format(endtime) },
rowHeight, minY);
yStart = (Float) r1[1];
Object[] r2 = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart - 2, tableWidth, colWidths,
new String[] { "单据编号", "生产订单编号", "产品名称", "产品编码", "数量", "生产车间", "次数" }, rowHeight, minY);
yStart = (Float) r2[1];
Object[] r3 = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart - 2, tableWidth, colWidths, new String[] {
productionOrderNumber, combinedVo.getOrderNumber(), combinedVo.getMaterialName(), combinedVo.getMaterialCode(),
String.valueOf(fmoQty), fDepartmentName, "1", "" }, rowHeight, minY);
yStart = (Float) r3[1];
float[] processColWidths = scaleWidths(new float[] { 35, 78, 35, 55, 45, 170, 55, 60, 60 }, tableWidth);
yStart = yStart - 6;
Object[] headerResult = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart, tableWidth, processColWidths,
new String[] { "工序号", "工序名称", "数量", "工作中心", "工时(分)", "工序说明", "控制码", "开始时间", "结束时间" },
rowHeight, minY);
yStart = (Float) headerResult[1];
if (processes != null) {
SimpleDateFormat formatte1r = new SimpleDateFormat("yyyy-MM-dd");
for (PlannedProcessVo process : processes) {
yStart = yStart - rowHeight;
Object[] rowResult = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart, tableWidth, processColWidths, new String[] {
String.valueOf(process.getFOperNumber()), process.getFProcessName(),
String.valueOf(process.getFOperQty()),
process.getFWorkCenterName(), String.format("%.2f", process.getFActivity1BaseQty()),
process.getFOperDescription(),
process.getFOptCtrlCodeIFName(),
process.getFSeqPlanStartTime() != null ? formatte1r.format(process.getFSeqPlanStartTime()) : "",
process.getFSeqPlanFinishTime() != null ? formatte1r.format(process.getFSeqPlanFinishTime()) : ""
}, rowHeight, minY);
float nextY = (Float) rowResult[1];
if (nextY == yStart || nextY <= minY) {
break;
}
yStart = nextY;
}
}
yStart = yStart - 15;
if (yStart >= minY) {
contentStream.beginText();
contentStream.setFont(font, 10);
contentStream.newLineAtOffset(margin, yStart);
contentStream.showText("生产用料清单:" + materialCodeQr);
contentStream.endText();
PDImageXObject qrCode2 = generateQRCode(document, materialCodeQr, (int) qrSize, (int) qrSize);
float qrTopY = yStart + 10;
if (qrTopY - qrSize >= minY - 20) {
contentStream.drawImage(qrCode2, qrX, qrTopY - qrSize);
}
yStart = yStart - 12;
}
yStart = yStart - 2;
float[] materialColWidths = scaleWidths(new float[] { 25, 130, 110, 50, 35, 40, 40, 80, 50 }, tableWidth);
Object[] mHeader = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart, tableWidth, materialColWidths, new String[] {
"序号", "物料编码", "物料名称", "规格型号", "单位", "应发数", "已领数", "仓库", "备注"
}, rowHeight, minY);
yStart = (Float) mHeader[1];
List<MaterialUsageDTO> materialUsageDTOList = combinedVo.getMaterialUsageDTOList();
if (materialUsageDTOList != null && !materialUsageDTOList.isEmpty()) {
for (int i = 0; i < materialUsageDTOList.size(); i++) {
MaterialUsageDTO material = materialUsageDTOList.get(i);
Object[] mr = drawTableRowWithWrapNoPageBreak(contentStream, font, fontSize, margin, yStart - 2, tableWidth, materialColWidths, new String[] {
String.valueOf(i + 1), material.getMaterialCode(),
material.getMaterialName(),
material.getSpecification(), material.getUnit(),
String.valueOf(material.getRequiredQty()),
String.valueOf(material.getPickedQty()),
material.getStockName(),
material.getRemarks() != null ? material.getRemarks() : ""
}, rowHeight, minY);
float nextY = (Float) mr[1];
if (nextY == yStart - 2 || nextY <= minY) {
break;
}
yStart = nextY;
}
}
}
private static float[] scaleWidths(float[] widths, float targetTotal) {
float sum = 0f;
for (float w : widths) {
sum += w;
}
if (sum <= 0f || targetTotal <= 0f) {
return widths;
}
float scale = targetTotal / sum;
float[] scaled = new float[widths.length];
for (int i = 0; i < widths.length; i++) {
scaled[i] = widths[i] * scale;
}
return scaled;
}
private static float drawTableRowNoPageBreak(PDPageContentStream contentStream, PDType0Font font, float fontSize, float margin,
float yStart, float tableWidth, float[] colWidths, String[] cells, float rowHeight, float minY) throws IOException {
if (yStart - rowHeight < minY) {
return yStart;
}
float nextX = margin;
float nextY = yStart;
int maxColumns = Math.min(cells.length, colWidths.length);
for (int i = 0; i < maxColumns; i++) {
contentStream.beginText();
contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(nextX + 4, nextY - (fontSize + 4));
String cellText = cells[i] != null ? cells[i].replace("\n", "") : "";
contentStream.showText(cellText);
contentStream.endText();
contentStream.moveTo(nextX, nextY);
contentStream.lineTo(nextX, nextY - rowHeight);
contentStream.lineTo(nextX + colWidths[i], nextY - rowHeight);
contentStream.lineTo(nextX + colWidths[i], nextY);
contentStream.closeAndStroke();
nextX += colWidths[i];
}
return yStart - rowHeight;
}
private static Object[] drawTableRowWithWrapNoPageBreak(PDPageContentStream contentStream, PDType0Font font, float fontSize,
float margin, float yStart, float tableWidth, float[] colWidths, String[] cells, float baseRowHeight, float minY)
throws IOException {
float nextX = margin;
float nextY = yStart;
int maxColumns = Math.min(cells.length, colWidths.length);
List<List<String>> allCellLines = new ArrayList<List<String>>();
int maxLines = 1;
for (int i = 0; i < maxColumns; i++) {
String cellText = cells[i] != null ? cells[i] : "";
List<String> lines = wrapText(cellText, font, fontSize, colWidths[i]);
allCellLines.add(lines);
maxLines = Math.max(maxLines, lines.size());
}
float actualRowHeight = baseRowHeight * maxLines;
if (nextY - actualRowHeight < minY) {
return new Object[]{contentStream, yStart};
}
nextX = margin;
for (int i = 0; i < maxColumns; i++) {
List<String> lines = allCellLines.get(i);
contentStream.moveTo(nextX, nextY);
contentStream.lineTo(nextX, nextY - actualRowHeight);
contentStream.lineTo(nextX + colWidths[i], nextY - actualRowHeight);
contentStream.lineTo(nextX + colWidths[i], nextY);
contentStream.closeAndStroke();
for (int lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
String line = lines.get(lineIndex);
if (!line.isEmpty()) {
contentStream.beginText();
contentStream.setFont(font, fontSize);
float textY = nextY - (fontSize + 4) - (lineIndex * (fontSize + 1));
contentStream.newLineAtOffset(nextX + 4, textY);
contentStream.showText(line);
contentStream.endText();
}
}
nextX += colWidths[i];
}
return new Object[]{contentStream, nextY - actualRowHeight};
}
private static String createSinglePdf(CombinedDTO combinedVo, String rooteProdet, String directoryPath, String fontPath) {
if (combinedVo == null) {
return null;
}
String materialCode1 = combinedVo.getMaterialCode();
String productionOrderNumber;
Date starttime;
Date endtime;
String materialCode;
Long fmoQty;
String fDepartmentName;
List<PlannedProcessVo> processes = combinedVo.getProcesses();
if (processes != null && !processes.isEmpty()) {
productionOrderNumber = String.valueOf(processes.get(0).getFBillNo());
starttime = processes.get(0).getFPlanStartTime();
endtime = processes.get(0).getFPlanFinishTime();
if (combinedVo.getMaterialUsageDTOList() != null && !combinedVo.getMaterialUsageDTOList().isEmpty()) {
materialCode = String.valueOf(combinedVo.getMaterialUsageDTOList().get(0).getBillNumber());
} else {
materialCode = "0";
}
fmoQty = processes.get(0).getFMOQty();
fDepartmentName = processes.get(0).getFWorkCenterName();
} else {
productionOrderNumber = "A";
starttime = new Date();
endtime = new Date();
materialCode = "0";
fmoQty = 0L;
fDepartmentName = "";
}
String pdfFileName = fDepartmentName + "_" + (materialCode1 != null ? materialCode1.replace("/", "_") : "") + "_独立" + ".pdf";
String departmentFolderName = fDepartmentName;
if (departmentFolderName == null || departmentFolderName.trim().isEmpty()) {
String[] parts = pdfFileName.split("_");
departmentFolderName = parts.length > 0 ? parts[0] : pdfFileName.substring(0, Math.min(4, pdfFileName.length()));
}
File departmentFolder = new File(directoryPath, departmentFolderName);
if (!departmentFolder.exists()) {
departmentFolder.mkdirs();
}
String pdfPath = departmentFolder.getAbsolutePath() + "\\" + fDepartmentName + "_" + (materialCode1 != null ? materialCode1.replace("/", "_") : "") + "_" + productionOrderNumber + ".pdf";
PDDocument document = null;
try {
document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDType0Font font;
try {
font = PDType0Font.load(document, new File(fontPath));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Font file not found", e);
}
PDPageContentStream contentStream = new PDPageContentStream(document, page);
float margin = 15;
float yStart = 740;
float rowHeight = 20f;
float pageHeight = page.getMediaBox().getHeight();
float pageWidth = page.getMediaBox().getWidth();
float qrSize = 90f;
float qrGap = 5f;
float tableWidth = pageWidth - 2 * margin - (qrSize + qrGap);
float qrX = pageWidth - margin - qrSize;
PDImageXObject qrCode1 = generateQRCode(document, productionOrderNumber, (int)qrSize, (int)qrSize);
contentStream.drawImage(qrCode1, qrX, yStart - qrSize + 25);
SimpleDateFormat formatte11r = new SimpleDateFormat("yyyy-MM-dd");
float[] colWidths = scaleWidths(new float[] { 60, 95, 110, 110, 60, 60, 55 }, tableWidth);
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths,
new String[] { "生产令号", rooteProdet, "开始时间", formatte11r.format(starttime), "结束时间", formatte11r.format(endtime) },
document, page, rowHeight, pageHeight);
yStart -= rowHeight;
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths,
new String[] { "单据编号", "生产订单编号", "产品名称", "产品编码", "数量", "生产车间", "次数" }, document, page,
rowHeight, pageHeight);
yStart -= rowHeight;
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, colWidths, new String[] {
productionOrderNumber, combinedVo.getOrderNumber(), combinedVo.getMaterialName(), combinedVo.getMaterialCode(),
String.valueOf(fmoQty), fDepartmentName, "1", "" }, document, page, rowHeight, pageHeight);
float processTableStartY = yStart - 2 * rowHeight - 10;
float[] processColWidths = scaleWidths(new float[] { 35, 78, 35, 55, 45, 170, 55, 60, 60 }, tableWidth);
yStart = processTableStartY;
Object[] result = drawTableRowWithWrap(contentStream, font, margin, yStart, tableWidth, processColWidths, new String[] {
"工序号", "工序名称", "数量", "工作中心", "工时(分)", "工序说明", "控制码", "开始时间", "结束时间" }, document, page,
rowHeight, pageHeight);
contentStream = (PDPageContentStream) result[0];
yStart = (Float) result[1];
if (processes != null) {
for (PlannedProcessVo process : processes) {
SimpleDateFormat formatte1r = new SimpleDateFormat("yyyy-MM-dd");
Object[] processResult = drawTableRowWithWrap(contentStream, font, margin, yStart, tableWidth, processColWidths, new String[] {
String.valueOf(process.getFOperNumber()), process.getFProcessName(),
String.valueOf(process.getFOperQty()),
process.getFWorkCenterName(), String.format("%.2f", process.getFActivity1BaseQty()),
process.getFOperDescription(),
process.getFOptCtrlCodeIFName(),
process.getFSeqPlanStartTime() != null ? formatte1r.format(process.getFSeqPlanStartTime()) : "",
process.getFSeqPlanFinishTime() != null ? formatte1r.format(process.getFSeqPlanFinishTime()) : ""
}, document, page, rowHeight, pageHeight);
contentStream = (PDPageContentStream) processResult[0];
yStart = (Float) processResult[1];
}
}
yStart = yStart - 25;
contentStream.beginText();
contentStream.setFont(font, 12);
contentStream.newLineAtOffset(margin, yStart);
contentStream.showText("生产用料清单:");
contentStream.showText(materialCode);
contentStream.endText();
PDImageXObject qrCode2 = generateQRCode(document, materialCode, (int)qrSize, (int)qrSize);
float qrTopY = yStart + 10;
contentStream.drawImage(qrCode2, qrX, qrTopY - qrSize);
yStart = yStart - 15;
float[] materialColWidths = scaleWidths(new float[] { 25, 130, 110, 50, 35, 40, 40, 80, 50 }, tableWidth);
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths, new String[] {
"序号", "物料编码", "物料名称", "规格型号", "单位", "应发数", "已领数", "仓库", "备注"
}, document, page, rowHeight, pageHeight);
List<MaterialUsageDTO> materialUsageDTOList = combinedVo.getMaterialUsageDTOList();
if (materialUsageDTOList != null && !materialUsageDTOList.isEmpty()) {
for (int i = 0; i < materialUsageDTOList.size(); i++) {
yStart -= rowHeight;
if (yStart - rowHeight < margin) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
yStart = pageHeight - margin;
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths, new String[] {
"序号", "物料编码", "物料名称", "规格型号", "单位", "应发数", "已领数", "仓库", "备注"
}, document, page, rowHeight, pageHeight);
yStart -= rowHeight;
}
MaterialUsageDTO material = materialUsageDTOList.get(i);
contentStream = drawTableRow(contentStream, font, margin, yStart, tableWidth, materialColWidths,
new String[] {
String.valueOf(i + 1), material.getMaterialCode(),
material.getMaterialName(),
material.getSpecification(), material.getUnit(),
String.valueOf(material.getRequiredQty()),
String.valueOf(material.getPickedQty()),
material.getStockName(),
material.getRemarks() != null ? material.getRemarks() : ""
}, document, page, rowHeight, pageHeight);
}
}
contentStream.close();
document.save(pdfPath);
return pdfPath;
} catch (IOException | WriterException e) {
e.printStackTrace();
return null;
} finally {
if (document != null) {
try {
document.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
/**
* 创建ZIP文件
*

View File

@ -2,13 +2,9 @@ package com.ruoyi.system.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.domain.MrpResultCheck;
import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.domain.ProcessRoute;
import com.ruoyi.system.domain.bo.FigureSaveBo;
import com.ruoyi.system.domain.dto.ProcessRouteExcelDTO;
import com.ruoyi.system.domain.dto.ProcessRoutePushResultDTO;
import com.ruoyi.system.domain.dto.ProcessRouteXuDTO;
import com.ruoyi.system.domain.dto.excuteDrawing.RigidChainModelDTO;
import com.ruoyi.system.domain.vo.OverdueProjectVo;
import com.ruoyi.system.domain.vo.ProcessOrderProVo;
@ -89,22 +85,22 @@ public interface IProcessOrderProService {
List<OverdueProjectVo> getOverdueProjects();
List<ProcessRouteExcelDTO> getRouteAndBomDetail(List<ProcessRoute> routlist,List<ProductionOrderVo> processDataList,ProcessOrderPro orderPro);
List<ProcessRoute> getRouteAndBomDetail(List<ProcessRoute> routlist, List<ProductionOrderVo> processDataList, ProcessOrderPro orderPro);
R<String> getRouteLog(Long id) throws JsonProcessingException;
SseEmitter startDrawingSse(Long id);
R<String> downloadDwg(List<Long> ids);
R<String> downloadDwg(Long id);
/**
* 推送钉钉卡片
* @param id 项目ID
*/
void pushDingTalkCard(Long id);
/**
* 刷新并计算项目延期状态
*/
void pushDingTalkCard(Long id);
/**
* 刷新并计算项目延期状态
*/
void refreshDelayStatus();
}

View File

@ -13,6 +13,7 @@ import com.ruoyi.system.jdmain.rouplan.Model;
import com.ruoyi.system.domain.vo.ProcessRouteVo;
import com.ruoyi.system.domain.vo.ProductionOrderVo;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
@ -95,7 +96,7 @@ public interface IProcessRouteService {
/**
* 生成这个项目的pdf
*/
String generatePDFs(String rooteProdet);
String generatePDFs(String rooteProdet) throws IOException;
/**
* 更新计划时间
*/

View File

@ -0,0 +1,49 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.SfcOperationPlanningDetail;
import com.ruoyi.system.domain.vo.SfcOperationPlanningDetailVo;
import com.ruoyi.system.domain.bo.SfcOperationPlanningDetailBo;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 工序计划明细Service接口
*
* @author ruoyi
* @date 2026-04-10
*/
public interface ISfcOperationPlanningDetailService {
/**
* 查询工序计划明细
*/
SfcOperationPlanningDetailVo queryById(Long id);
/**
* 查询工序计划明细列表
*/
TableDataInfo<SfcOperationPlanningDetailVo> queryPageList(SfcOperationPlanningDetailBo bo, PageQuery pageQuery);
/**
* 查询工序计划明细列表
*/
List<SfcOperationPlanningDetailVo> queryList(SfcOperationPlanningDetailBo bo);
/**
* 新增工序计划明细
*/
Boolean insertByBo(SfcOperationPlanningDetailBo bo);
/**
* 修改工序计划明细
*/
Boolean updateByBo(SfcOperationPlanningDetailBo bo);
/**
* 校验并批量删除工序计划明细信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,60 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.SfcOperationPlanningMain;
import com.ruoyi.system.domain.dto.OperationPlanResultDTO;
import com.ruoyi.system.domain.vo.SfcOperationPlanningMainVo;
import com.ruoyi.system.domain.bo.SfcOperationPlanningMainBo;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 工序计划主Service接口
*
* @author ruoyi
* @date 2026-04-10
*/
public interface ISfcOperationPlanningMainService {
/**
* 查询工序计划主
*/
SfcOperationPlanningMainVo queryById(Long id);
/**
* 查询工序计划主列表
*/
TableDataInfo<SfcOperationPlanningMainVo> queryPageList(SfcOperationPlanningMainBo bo, PageQuery pageQuery);
/**
* 查询工序计划主列表
*/
List<SfcOperationPlanningMainVo> queryList(SfcOperationPlanningMainBo bo);
/**
* 新增工序计划主
*/
Boolean insertByBo(SfcOperationPlanningMainBo bo);
/**
* 修改工序计划主
*/
Boolean updateByBo(SfcOperationPlanningMainBo bo);
/**
* 校验并批量删除工序计划主信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 同步K3工序计划数据
*/
Boolean syncOperationPlanning();
/**
* 分页查询工序计划主及明细级联数据
*/
TableDataInfo<OperationPlanResultDTO> queryCascadePageList(SfcOperationPlanningMainBo bo, PageQuery pageQuery);
}

View File

@ -116,11 +116,12 @@ public class PartCostServiceImpl implements IPartCostService {
@Override
public boolean getObtainPartData() {
System.out.println("1 开始");
List<PartCost> list = JdUtil.getObtainPartData();
if (list == null || list.isEmpty()) {
return false;
}
System.out.println("2 获取数据:" + (list==null?0:list.size()));
Map<String, PartCost> uniq = new LinkedHashMap<>();
for (PartCost pc : list) {
if (pc == null) continue;

View File

@ -27,7 +27,9 @@ import com.ruoyi.common.utils.HttpUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.DeleteFile;
import com.ruoyi.common.utils.file.PDFDocHelper;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.controller.ProcessOrderProController;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.domain.bo.ProcessOrderProBo;
import com.ruoyi.system.domain.dto.ProcessRouteExcelDTO;
@ -742,6 +744,21 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
// 2. 查询生产令号是否重复
ProcessOrderPro processOrderPro = checkProductionOrderExists(orderPro.getProductionOrderNo());
// 2.1 检查出图服务是否繁忙
try {
String preStatusResp = HttpUtils.sendGet(statusUrl, "UTF-8");
JSONObject preJson = JSONObject.parseObject(preStatusResp);
String preStatus = preJson.getString("status");
if ("busy".equals(preStatus)) {
throw new RuntimeException("出图服务忙,有其他人在出图中,请稍后再试");
}
} catch (Exception e) {
if (e instanceof RuntimeException && e.getMessage().contains("出图服务忙")) {
throw (RuntimeException) e;
}
log.warn("检查出图服务状态失败,继续执行: {}", e.getMessage());
}
// 3. 构建路径
String dirPath = buildDirectoryPath(orderPro.getProductionOrderNo());
@ -753,13 +770,11 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
pwProductionBill.setProduct(productInfos);
// 6. 调用API
String apiResult = null;
try {
return callDrawingApi(pwProductionBill, orderPro);
} catch (Exception e) {
log.error("调用出图API失败", e);
throw e;
} finally {
// 无论成功失败都发送通知和文件
apiResult = callDrawingApi(pwProductionBill, orderPro);
// 只有成功才发送通知和文件
CompletableFuture.runAsync(() -> {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@ -789,7 +804,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
}
if (po.getIsEnterpriseStandard() != null) {
markdownText.append("> **是否企标**: ").append(po.getIsEnterpriseStandard() == "1" ? "" : "").append("\n\n");
markdownText.append("> **是否企标**: ").append(Objects.equals(po.getIsEnterpriseStandard(), "1") ? "" : "").append("\n\n");
}
String bomStatus = null;
@ -827,63 +842,44 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
}
// 下载并发送Excel文件
log.info("开始下载并发送Excel文件: {}", orderPro.getProductionOrderNo());
log.info("开始生成并发送Excel模板文件: {}", orderPro.getProductionOrderNo());
// 1. 解析年份
String productionCode = orderPro.getProductionOrderNo();
String year = "2025"; // 默认
if (productionCode != null) {
if (productionCode.startsWith("EY")) {
if (productionCode.length() >= 4) {
try {
Integer.parseInt(productionCode.substring(2, 4));
year = "20" + productionCode.substring(2, 4);
} catch (Exception ignore) {
}
}
} else {
int firstDash = productionCode.indexOf("-");
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
try {
Integer.parseInt(productionCode.substring(firstDash + 1, firstDash + 3));
year = "20" + productionCode.substring(firstDash + 1, firstDash + 3);
} catch (Exception ignore) {
}
}
}
String filePath = null;
try {
ProcessOrderProController controller = SpringUtils.getBean(ProcessOrderProController.class);
filePath = controller.generateRoute3Excel(orderPro.getId(), true);
} catch (Exception e) {
log.error("生成工艺计划表模板Excel失败", e);
}
// 2. 构建FTP路径和本地路径
String ftpPath = "/" + year + "/" + productionCode;
String localPath = "D:\\file\\";
// 3. 下载文件只下载汇总表.xlsx
R<String> downloadResult = FtpUtil.downloadFtpDirectoryFiles("192.168.5.18", "admin", "hbyt2025", 21, ftpPath, localPath, "汇总表.xlsx");
if (downloadResult == null || downloadResult.getCode() != 200) {
log.warn("Excel文件下载可能失败: productionCode={}, ftpPath={}, result={}", productionCode, ftpPath, downloadResult);
}
// 4. 构建本地文件路径并发送钉钉文件
String filePath = localPath + productionCode + "汇总表.xlsx";
File excelFile = new File(filePath);
if (excelFile.exists() && excelFile.length() > 0) {
// Upload media
String mediaId = mediaService.uploadMedia("file", filePath);
// Send file给个人
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
robotGroupService.sendFileToUser(dingTalkProperties.getNotifyUserId(), mediaId, excelFile.getName());
log.info("Excel文件发送成功");
if (filePath != null) {
File excelFile = new File(filePath);
if (excelFile.exists() && excelFile.length() > 0) {
// Upload media
String mediaId = mediaService.uploadMedia("file", filePath);
// Send file给个人
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
robotGroupService.sendFileToUser(dingTalkProperties.getNotifyUserId(), mediaId, excelFile.getName());
log.info("Excel模板文件发送成功");
} else {
log.warn("未配置钉钉通知个人ID(notifyUserId)跳过发送Excel文件");
}
// 发送完毕后删除生成的临时文件
excelFile.delete();
} else {
log.warn("未配置钉钉通知个人ID(notifyUserId),跳过发送Excel文件");
log.warn("Excel文件未找到或为空: {}", filePath);
}
} else {
log.warn("Excel文件未找到或为空: {}", filePath);
}
} catch (Exception ex) {
log.error("发送出图通知/文件失败", ex);
}
});
return apiResult;
} catch (Exception e) {
log.error("调用出图API失败", e);
throw e;
}
}
@ -1264,13 +1260,6 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
String drawingDate = JSONObject.toJSONString(pwProductionBill);
log.info("请求出图报文=====>{}", drawingDate);
String preStatusResp = HttpUtils.sendGet(statusUrl, "UTF-8");
JSONObject preJson = JSONObject.parseObject(preStatusResp);
String preStatus = preJson.getString("status");
if ("busy".equals(preStatus)) {
throw new RuntimeException("出图服务忙,请稍后再试");
}
// 1. 先发起生成请求
String response = HttpUtils.sendPost(apiUrl, drawingDate);
log.info("出图接口响应=====>{}", response);
@ -1357,78 +1346,134 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
/**
* 下载DWG文件
*
* @param ids FigureSaveVo ID列表
* @param id FigureSaveVo ID
* @return 结果
*/
@Override
public R<String> downloadDwg(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return R.fail("ID列表为空");
}
// 只下载第一个文件
FigureSave figureSave = figureSaveMapper.selectById(ids.get(0));
if (figureSave == null) {
return R.fail("未找到对应的产品信息");
}
// 创建临时目录
String tempBaseDir = "D:\\dwg_temp\\" + System.currentTimeMillis();
File tempDir = new File(tempBaseDir);
if (!tempDir.exists() && !tempDir.mkdirs()) {
return R.fail("创建临时目录失败");
public R<String> downloadDwg(Long id) {
if (id == null) {
return R.fail("ID为空");
}
try {
String productionCode = figureSave.getProductionCode();
String figureName = figureSave.getFigureName();
if (StringUtils.isEmpty(productionCode) || StringUtils.isEmpty(figureName)) {
return R.fail("产品信息不完整");
// 根据id查询对应的图号
FigureSave figureSave = figureSaveMapper.selectById(id);
if (figureSave == null) {
return R.fail("未找到对应的产品信息");
}
// 1. 解析年份
// 假设这些文件属于同一个项目生产令号
String productionCode = figureSave.getProductionCode();
if (StringUtils.isBlank(productionCode)) {
return R.fail("产品信息中生产令号为空");
}
// 解析年份
String year = "2025"; // 默认
if (productionCode.startsWith("EY")) {
if (productionCode.length() >= 4) {
String yearShort = productionCode.substring(2, 4);
try {
Integer.parseInt(productionCode.substring(2, 4));
year = "20" + productionCode.substring(2, 4);
} catch (Exception ignore) {
int y = Integer.parseInt(yearShort);
if (y > 10 && y < 50) {
year = "20" + yearShort;
}
} catch (NumberFormatException e) {
// 解析失败保持默认
}
}
} else {
int firstDash = productionCode.indexOf("-");
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
String yearShort = productionCode.substring(firstDash + 1, firstDash + 3);
try {
Integer.parseInt(productionCode.substring(firstDash + 1, firstDash + 3));
year = "20" + productionCode.substring(firstDash + 1, firstDash + 3);
int yearInt = Integer.parseInt(yearShort);
if (yearInt > 10 && yearInt < 50) {
year = "20" + yearShort;
} else {
if (firstDash >= 4 && productionCode.startsWith("EY")) {
String altYearShort = productionCode.substring(2, 4);
try {
Integer.parseInt(altYearShort);
year = "20" + altYearShort;
} catch (Exception ex) {}
} else if (firstDash >= 2) {
String altYearShort = productionCode.substring(firstDash - 2, firstDash);
try {
Integer.parseInt(altYearShort);
year = "20" + altYearShort;
} catch (Exception ex) {}
}
}
} catch (Exception ignore) {
}
}
}
// 2. 构建FTP路径和本地路径
// FTP路径: /{year}/{productionCode}/{figureName}.dwg
String ftpPath = "/" + year + "/" + productionCode + "/" + figureName + ".dwg";
String fileName = figureName + ".dwg";
// 本地存放路径
String localFilePath = tempBaseDir + File.separator + fileName;
log.info("开始下载DWG: ftpPath={}, localFilePath={}", ftpPath, localFilePath);
// 3. 下载文件
boolean downloadSuccess = FtpUtil.downloadSingleFile("192.168.5.18", "admin", "hbyt2025", 21, ftpPath, localFilePath);
if (downloadSuccess) {
File downloadedFile = new File(localFilePath);
if (downloadedFile.exists() && downloadedFile.length() > 0) {
// 直接返回文件路径无需打包
return R.ok("操作成功", localFilePath);
} else {
return R.fail("文件下载失败:文件为空");
}
// 生成到本地的路径: 根据项目号生成文件夹
String localBaseDir = buildWorkDirectory(productionCode);
String targetDir = localBaseDir + productionCode + "/dwg";
File targetDirectory = new File(targetDir);
if (!targetDirectory.exists()) {
targetDirectory.mkdirs();
} else {
return R.fail("文件下载失败");
// 清空之前的旧文件
File[] files = targetDirectory.listFiles();
if (files != null) {
for (File f : files) {
f.delete();
}
}
}
String figureNumber = figureSave.getFigureNumber();
if (StringUtils.isBlank(figureNumber)) {
return R.fail("图号为空无法下载DWG文件");
}
// 替换图号中的 / -
String safeFigureNumber = figureNumber.replace("/", "-");
String dwgFileName = safeFigureNumber + ".dwg";
// 修正有些 DWG 文件就放在根目录下所以直接用生产令号作为远程目录
// 或者有可能 DWG 放在 ` /{year}/{productionCode}/dwg `
String rootRemoteDir = "/" + year + "/" + productionCode;
log.info("尝试下载DWG文件, 远程目录: {}, 本地目录: {}, 文件名: {}", rootRemoteDir, targetDir, dwgFileName);
// 参考单元测试调用 FtpUtil 下载文件
R<String> downloadResult = FtpUtil.downloadFtpDirectoryFiles(
"192.168.5.18", "admin", "hbyt2025", 21, rootRemoteDir, targetDir, dwgFileName
);
if (downloadResult.getCode() != 200) {
// 尝试加一层子目录去下载防止它藏在图号同名文件夹下
String subRemoteDir = "/" + year + "/" + productionCode + "/" + safeFigureNumber;
log.warn("根目录 {} 下载失败,尝试从子目录 {} 下载", rootRemoteDir, subRemoteDir);
downloadResult = FtpUtil.downloadFtpDirectoryFiles(
"192.168.5.18", "admin", "hbyt2025", 21, subRemoteDir, targetDir, dwgFileName
);
}
if (downloadResult.getCode() != 200) {
// 有的旧数据可能在 /2026/KC-26-026-FS/dwg
String dwgRemoteDir = "/" + year + "/" + productionCode + "/dwg";
log.warn("子目录下载也失败尝试从dwg子目录 {} 下载", dwgRemoteDir);
downloadResult = FtpUtil.downloadFtpDirectoryFiles(
"192.168.5.18", "admin", "hbyt2025", 21, dwgRemoteDir, targetDir, dwgFileName
);
}
if (downloadResult.getCode() != 200) {
return R.fail("DWG文件下载失败: " + dwgFileName + " - " + downloadResult.getMsg());
}
// 直接返回目标文件夹路径
return R.ok(targetDir);
} catch (Exception e) {
log.error("下载DWG过程中发生异常", e);
return R.fail("下载异常: " + e.getMessage());
log.error("下载DWG文件失败", e);
return R.fail("下载DWG文件失败: " + e.getMessage());
}
}
@ -1491,8 +1536,10 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
if (productionCode.length() >= 4) {
String yearShort = productionCode.substring(2, 4);
try {
Integer.parseInt(yearShort);
year = "20" + yearShort;
int y = Integer.parseInt(yearShort);
if (y > 10 && y < 50) {
year = "20" + yearShort;
}
} catch (NumberFormatException e) {
// 解析失败保持默认
}
@ -1503,8 +1550,31 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
String yearShort = productionCode.substring(firstDash + 1, firstDash + 3);
try {
Integer.parseInt(yearShort); // 验证是否为数字
year = "20" + yearShort;
// 简单的年份合理性校验比如 > 10 < 50
int yearInt = Integer.parseInt(yearShort);
if (yearInt > 10 && yearInt < 50) {
year = "20" + yearShort;
} else {
// 如果提取出来的像 00可能格式是 EY2609-002横杠前其实是年份 26
if (firstDash >= 4 && productionCode.startsWith("EY")) {
String altYearShort = productionCode.substring(2, 4);
try {
Integer.parseInt(altYearShort);
year = "20" + altYearShort;
} catch (Exception ex) {
// ignore
}
} else if (firstDash >= 2) {
// 比如 XX26-001可能是字母加数字
String altYearShort = productionCode.substring(firstDash - 2, firstDash);
try {
Integer.parseInt(altYearShort);
year = "20" + altYearShort;
} catch (Exception ex) {
// ignore
}
}
}
} catch (NumberFormatException e) {
// 解析失败保持默认
}
@ -1958,15 +2028,13 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
e.printStackTrace();
return "上传到FTP失败";
}
if (!ftpSuccess) {
return "上传到FTP失败";
}
// 5. 反写FTP路径到数据库
figureSave.setDrawPath("E:" + basePath + ftpPath + fullLocalPath);
figureSave.setUploadTime(new Date());
int updateCount = figureSaveMapper.updateById(figureSave);
return updateCount == 1 ? "上传成功" : "上传失败";
}
@ -2047,7 +2115,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
* @return
*/
@Override
public List<ProcessRouteExcelDTO> getRouteAndBomDetail(List<ProcessRoute> routlist, List<ProductionOrderVo> processDataList, ProcessOrderPro orderPro) {
public List<ProcessRoute> getRouteAndBomDetail(List<ProcessRoute> routlist, List<ProductionOrderVo> processDataList, ProcessOrderPro orderPro) {
String proRoot = orderPro.getProductionOrderNo();
// 查出所有工艺路线和BOM
@ -2064,7 +2132,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
copyPropertiesIgnoreNull(source, route); // routlist 的非空字段补充 allRoutes
}
}
List<ProcessRouteExcelDTO> resultList = new ArrayList<>();
List<ProcessRoute> resultList = new ArrayList<>();
// 规范化 key
Function<String, String> normalize = s -> s == null ? "" : s.trim().toUpperCase();
@ -2105,21 +2173,21 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
if (maxSize == 0 && orderVo != null) maxSize = 1;
for (int i = 0; i < maxSize; i++) {
ProcessRouteExcelDTO dto = new ProcessRouteExcelDTO();
ProcessRoute dto = new ProcessRoute();
// 补充名称/材质/单重字段
dto.setMaterialCode(originalMaterialCode);
dto.setMaterialName(originalMaterialName);
dto.setMaterial(originalMaterial);
dto.setDiscWeight(String.valueOf(originalUnitWeight));
dto.setUnitQuantity(String.valueOf(unitQuantity));
dto.setBatchQuantity(String.valueOf(batchQuantity));
dto.setDiscWeight(Double.valueOf(String.valueOf(originalUnitWeight)));
dto.setUnitQuantity(Double.valueOf(String.valueOf(unitQuantity)));
dto.setBatchQuantity(Long.parseLong(String.valueOf(String.valueOf(batchQuantity))) );
// 订单数量信息
if (orderVo != null) {
dto.setRouteDescription(orderVo.getProductionOrderNo());
dto.setUnitQuantity(String.valueOf(orderVo.getQuantity()));
dto.setBatchQuantity(orderVo.getBatchQuantity());
dto.setUnitQuantity(Double.valueOf(String.valueOf(orderVo.getQuantity())));
dto.setBatchQuantity(Long.valueOf(orderVo.getBatchQuantity()));
}
// 工艺部分

View File

@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
@ -1441,7 +1442,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
}
@Override
public String generatePDFs(String rooteProdet) {
public String generatePDFs(String rooteProdet) throws IOException {
List<CombinedDTO> selecPlanRouteList = getSelecPlanRouteList(rooteProdet);
String pathZIP = PDFGenerator.writeToPdf(selecPlanRouteList, rooteProdet);
return pathZIP;

View File

@ -0,0 +1,129 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.bo.SfcOperationPlanningDetailBo;
import com.ruoyi.system.domain.vo.SfcOperationPlanningDetailVo;
import com.ruoyi.system.domain.SfcOperationPlanningDetail;
import com.ruoyi.system.mapper.SfcOperationPlanningDetailMapper;
import com.ruoyi.system.service.ISfcOperationPlanningDetailService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 工序计划明细Service业务层处理
*
* @author ruoyi
* @date 2026-04-10
*/
@RequiredArgsConstructor
@Service
public class SfcOperationPlanningDetailServiceImpl implements ISfcOperationPlanningDetailService {
private final SfcOperationPlanningDetailMapper baseMapper;
/**
* 查询工序计划明细
*/
@Override
public SfcOperationPlanningDetailVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 查询工序计划明细列表
*/
@Override
public TableDataInfo<SfcOperationPlanningDetailVo> queryPageList(SfcOperationPlanningDetailBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SfcOperationPlanningDetail> lqw = buildQueryWrapper(bo);
Page<SfcOperationPlanningDetailVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询工序计划明细列表
*/
@Override
public List<SfcOperationPlanningDetailVo> queryList(SfcOperationPlanningDetailBo bo) {
LambdaQueryWrapper<SfcOperationPlanningDetail> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SfcOperationPlanningDetail> buildQueryWrapper(SfcOperationPlanningDetailBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SfcOperationPlanningDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getMainId() != null, SfcOperationPlanningDetail::getMainId, bo.getMainId());
lqw.eq(bo.getFid() != null, SfcOperationPlanningDetail::getFid, bo.getFid());
lqw.eq(bo.getEntityEntryId() != null, SfcOperationPlanningDetail::getEntityEntryId, bo.getEntityEntryId());
lqw.eq(bo.getOperNumber() != null, SfcOperationPlanningDetail::getOperNumber, bo.getOperNumber());
lqw.eq(bo.getOperQty() != null, SfcOperationPlanningDetail::getOperQty, bo.getOperQty());
lqw.eq(bo.getQualifiedQty() != null, SfcOperationPlanningDetail::getQualifiedQty, bo.getQualifiedQty());
lqw.eq(bo.getTransOutQty() != null, SfcOperationPlanningDetail::getTransOutQty, bo.getTransOutQty());
lqw.eq(bo.getUnqualifiedQty() != null, SfcOperationPlanningDetail::getUnqualifiedQty, bo.getUnqualifiedQty());
lqw.eq(bo.getWastageQty() != null, SfcOperationPlanningDetail::getWastageQty, bo.getWastageQty());
lqw.eq(bo.getTransInQty() != null, SfcOperationPlanningDetail::getTransInQty, bo.getTransInQty());
lqw.like(StringUtils.isNotBlank(bo.getDepartmentName()), SfcOperationPlanningDetail::getDepartmentName, bo.getDepartmentName());
lqw.like(StringUtils.isNotBlank(bo.getWorkCenterName()), SfcOperationPlanningDetail::getWorkCenterName, bo.getWorkCenterName());
lqw.like(StringUtils.isNotBlank(bo.getProcessName()), SfcOperationPlanningDetail::getProcessName, bo.getProcessName());
lqw.eq(StringUtils.isNotBlank(bo.getOperator()), SfcOperationPlanningDetail::getOperator, bo.getOperator());
lqw.eq(StringUtils.isNotBlank(bo.getOperStatus()), SfcOperationPlanningDetail::getOperStatus, bo.getOperStatus());
lqw.eq(bo.getSeqPlanStartTime() != null, SfcOperationPlanningDetail::getSeqPlanStartTime, bo.getSeqPlanStartTime());
lqw.eq(bo.getSeqPlanFinishTime() != null, SfcOperationPlanningDetail::getSeqPlanFinishTime, bo.getSeqPlanFinishTime());
lqw.eq(StringUtils.isNotBlank(bo.getOptCtrlCode()), SfcOperationPlanningDetail::getOptCtrlCode, bo.getOptCtrlCode());
lqw.eq(StringUtils.isNotBlank(bo.getKeyOper()), SfcOperationPlanningDetail::getKeyOper, bo.getKeyOper());
lqw.like(StringUtils.isNotBlank(bo.getActivityName()), SfcOperationPlanningDetail::getActivityName, bo.getActivityName());
lqw.eq(StringUtils.isNotBlank(bo.getOperDescription()), SfcOperationPlanningDetail::getOperDescription, bo.getOperDescription());
return lqw;
}
/**
* 新增工序计划明细
*/
@Override
public Boolean insertByBo(SfcOperationPlanningDetailBo bo) {
SfcOperationPlanningDetail add = BeanUtil.toBean(bo, SfcOperationPlanningDetail.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改工序计划明细
*/
@Override
public Boolean updateByBo(SfcOperationPlanningDetailBo bo) {
SfcOperationPlanningDetail update = BeanUtil.toBean(bo, SfcOperationPlanningDetail.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SfcOperationPlanningDetail entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除工序计划明细
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@ -0,0 +1,334 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.bo.SfcOperationPlanningMainBo;
import com.ruoyi.system.domain.vo.SfcOperationPlanningMainVo;
import com.ruoyi.system.domain.SfcOperationPlanningMain;
import com.ruoyi.system.mapper.SfcOperationPlanningMainMapper;
import com.ruoyi.system.mapper.SfcOperationPlanningDetailMapper;
import com.ruoyi.system.domain.SfcOperationPlanningDetail;
import com.ruoyi.system.runner.JdUtil;
import com.ruoyi.system.domain.dto.OperationPlanResultDTO;
import com.ruoyi.system.domain.dto.ProcessDetailDTO;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import cn.hutool.core.date.DateUtil;
import java.util.Date;
import com.ruoyi.system.service.ISfcOperationPlanningMainService;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.Comparator;
import java.util.LinkedHashMap;
/**
* 工序计划主Service业务层处理
*
* @author ruoyi
* @date 2026-04-10
*/
@RequiredArgsConstructor
@Service
public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlanningMainService {
private final SfcOperationPlanningMainMapper baseMapper;
private final SfcOperationPlanningDetailMapper detailMapper;
/**
* 同步K3工序计划数据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean syncOperationPlanning() {
List<OperationPlanResultDTO> structuredList = JdUtil.getStructuredOperationPlanningList();
if (structuredList == null || structuredList.isEmpty()) {
return true;
}
for (OperationPlanResultDTO resultDTO : structuredList) {
// 查询是否已经存在该主表记录 (根据 fid)
SfcOperationPlanningMain mainEntity = baseMapper.selectOne(Wrappers.<SfcOperationPlanningMain>lambdaQuery()
.eq(SfcOperationPlanningMain::getFid, resultDTO.getFid()));
if (mainEntity == null) {
mainEntity = new SfcOperationPlanningMain();
}
mainEntity.setFid(resultDTO.getFid());
mainEntity.setBillNo(resultDTO.getBillNo());
mainEntity.setMoNumber(resultDTO.getMoNumber());
mainEntity.setHbytSclh(resultDTO.getBatchNo());
mainEntity.setProductCode(resultDTO.getProductCode());
mainEntity.setProductName(resultDTO.getProductName());
mainEntity.setProSpecification(resultDTO.getProSpecification());
if (StringUtils.isNotBlank(resultDTO.getPlanStartTime())) {
mainEntity.setPlanStartTime(DateUtil.parse(resultDTO.getPlanStartTime()));
}
if (StringUtils.isNotBlank(resultDTO.getPlanFinishTime())) {
mainEntity.setPlanFinishTime(DateUtil.parse(resultDTO.getPlanFinishTime()));
}
if (mainEntity.getId() == null) {
baseMapper.insert(mainEntity);
} else {
baseMapper.updateById(mainEntity);
}
Long mainId = mainEntity.getId();
// 处理子表数据
List<ProcessDetailDTO> processList = resultDTO.getProcessList();
if (processList != null && !processList.isEmpty()) {
for (ProcessDetailDTO detailDTO : processList) {
// 查询是否已经存在该明细记录 (根据 fid entity_entry_id oper_number)
SfcOperationPlanningDetail detailEntity = detailMapper.selectOne(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.eq(SfcOperationPlanningDetail::getFid, resultDTO.getFid())
.eq(SfcOperationPlanningDetail::getEntityEntryId, detailDTO.getEntityEntryId())
.eq(detailDTO.getOperNo() != null, SfcOperationPlanningDetail::getOperNumber, detailDTO.getOperNo()));
if (detailEntity == null) {
detailEntity = new SfcOperationPlanningDetail();
}
detailEntity.setMainId(mainId);
detailEntity.setFid(resultDTO.getFid());
detailEntity.setEntityEntryId(detailDTO.getEntityEntryId());
if (detailDTO.getOperNo() != null) {
detailEntity.setOperNumber(Long.valueOf(detailDTO.getOperNo()));
}
detailEntity.setOperQty(detailDTO.getPlanQty() != null ? BigDecimal.valueOf(detailDTO.getPlanQty()) : null);
detailEntity.setQualifiedQty(detailDTO.getOkQty() != null ? BigDecimal.valueOf(detailDTO.getOkQty()) : null);
detailEntity.setTransOutQty(detailDTO.getTransOutQty() != null ? BigDecimal.valueOf(detailDTO.getTransOutQty()) : null);
detailEntity.setUnqualifiedQty(detailDTO.getNgQty() != null ? BigDecimal.valueOf(detailDTO.getNgQty()) : null);
detailEntity.setWastageQty(detailDTO.getWastageQty() != null ? BigDecimal.valueOf(detailDTO.getWastageQty()) : null);
detailEntity.setTransInQty(detailDTO.getInQty() != null ? BigDecimal.valueOf(detailDTO.getInQty()) : null);
detailEntity.setDepartmentName(detailDTO.getDepartment());
detailEntity.setWorkCenterName(detailDTO.getWorkCenter());
detailEntity.setProcessName(detailDTO.getProcessName());
detailEntity.setOperator(detailDTO.getOperator());
detailEntity.setOperStatus(detailDTO.getStatus());
if (StringUtils.isNotBlank(detailDTO.getSeqPlanStartTime())) {
detailEntity.setSeqPlanStartTime(DateUtil.parse(detailDTO.getSeqPlanStartTime()));
}
if (StringUtils.isNotBlank(detailDTO.getSeqPlanFinishTime())) {
detailEntity.setSeqPlanFinishTime(DateUtil.parse(detailDTO.getSeqPlanFinishTime()));
}
detailEntity.setOptCtrlCode(detailDTO.getOptCtrlCode());
detailEntity.setKeyOper(detailDTO.getKeyOper());
detailEntity.setActivityName(detailDTO.getActivity1Name());
detailEntity.setOperDescription(detailDTO.getOperDescription());
if (detailEntity.getId() == null) {
detailMapper.insert(detailEntity);
} else {
detailMapper.updateById(detailEntity);
}
}
}
}
return true;
}
/**
* 查询工序计划主
*/
@Override
public SfcOperationPlanningMainVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 查询工序计划主列表
*/
@Override
public TableDataInfo<SfcOperationPlanningMainVo> queryPageList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SfcOperationPlanningMain> lqw = buildQueryWrapper(bo);
Page<SfcOperationPlanningMainVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询工序计划主列表
*/
@Override
public List<SfcOperationPlanningMainVo> queryList(SfcOperationPlanningMainBo bo) {
LambdaQueryWrapper<SfcOperationPlanningMain> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
/**
* 分页查询工序计划主及明细级联数据
*/
@Override
public TableDataInfo<OperationPlanResultDTO> queryCascadePageList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
// 1. 构建主表查询条件
LambdaQueryWrapper<SfcOperationPlanningMain> lqw = buildQueryWrapper(bo);
// 过滤生产令号包含暂停的记录
lqw.notLike(SfcOperationPlanningMain::getHbytSclh, "暂停");
// 2. 分页查询主表数据
Page<SfcOperationPlanningMain> page = baseMapper.selectPage(pageQuery.build(), lqw);
List<SfcOperationPlanningMain> mainList = page.getRecords();
if (mainList == null || mainList.isEmpty()) {
return TableDataInfo.build(new ArrayList<>());
}
// 3. 提取当前页所有主表的 ID
List<Long> mainIds = mainList.stream().map(SfcOperationPlanningMain::getId).collect(Collectors.toList());
// 4. 根据主表 ID 批量查询子表数据并按工序号升序排序
List<SfcOperationPlanningDetail> detailList = detailMapper.selectList(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.in(SfcOperationPlanningDetail::getMainId, mainIds)
.orderByAsc(SfcOperationPlanningDetail::getOperNumber));
// 5. 按主表 ID 对子表数据进行分组
Map<Long, List<SfcOperationPlanningDetail>> detailMap = detailList.stream()
.collect(Collectors.groupingBy(SfcOperationPlanningDetail::getMainId));
// 6. 组装返回给前端的层级结构
List<OperationPlanResultDTO> resultList = new ArrayList<>();
for (SfcOperationPlanningMain main : mainList) {
OperationPlanResultDTO resultDTO = new OperationPlanResultDTO();
resultDTO.setMoNumber(main.getMoNumber());
resultDTO.setBatchNo(main.getHbytSclh());
resultDTO.setProductCode(main.getProductCode());
resultDTO.setProductName(main.getProductName());
resultDTO.setBillNo(main.getBillNo());
resultDTO.setProSpecification(main.getProSpecification());
resultDTO.setPlanStartTime(main.getPlanStartTime() != null ? DateUtil.formatDateTime(main.getPlanStartTime()) : null);
resultDTO.setPlanFinishTime(main.getPlanFinishTime() != null ? DateUtil.formatDateTime(main.getPlanFinishTime()) : null);
resultDTO.setFid(main.getFid());
// 获取原生Date以便于后续排序使用
resultDTO.setRawPlanStartTime(main.getPlanStartTime());
// 组装关联的子表数据
List<ProcessDetailDTO> processList = new ArrayList<>();
List<SfcOperationPlanningDetail> relatedDetails = detailMap.get(main.getId());
if (relatedDetails != null) {
for (SfcOperationPlanningDetail detail : relatedDetails) {
ProcessDetailDTO detailDTO = new ProcessDetailDTO();
detailDTO.setOperNo(detail.getOperNumber() != null ? detail.getOperNumber().intValue() : null);
detailDTO.setProcessName(detail.getProcessName());
detailDTO.setWorkCenter(detail.getWorkCenterName());
detailDTO.setPlanQty(detail.getOperQty() != null ? detail.getOperQty().doubleValue() : null);
detailDTO.setInQty(detail.getTransInQty() != null ? detail.getTransInQty().doubleValue() : null);
detailDTO.setOkQty(detail.getQualifiedQty() != null ? detail.getQualifiedQty().doubleValue() : null);
detailDTO.setNgQty(detail.getUnqualifiedQty() != null ? detail.getUnqualifiedQty().doubleValue() : null);
detailDTO.setTransOutQty(detail.getTransOutQty() != null ? detail.getTransOutQty().doubleValue() : null);
detailDTO.setWastageQty(detail.getWastageQty() != null ? detail.getWastageQty().doubleValue() : null);
detailDTO.setDepartment(detail.getDepartmentName());
detailDTO.setOperator(detail.getOperator());
// 状态映射
String statusStr = detail.getOperStatus();
if ("1".equals(statusStr)) detailDTO.setStatus("计划");
else if ("2".equals(statusStr)) detailDTO.setStatus("计划下达");
else if ("3".equals(statusStr)) detailDTO.setStatus("开工");
else if ("4".equals(statusStr)) detailDTO.setStatus("已完工");
else if ("5".equals(statusStr)) detailDTO.setStatus("结案");
else if ("6".equals(statusStr)) detailDTO.setStatus("结算");
else detailDTO.setStatus(statusStr);
detailDTO.setSeqPlanStartTime(detail.getSeqPlanStartTime() != null ? DateUtil.formatDateTime(detail.getSeqPlanStartTime()) : null);
detailDTO.setSeqPlanFinishTime(detail.getSeqPlanFinishTime() != null ? DateUtil.formatDateTime(detail.getSeqPlanFinishTime()) : null);
detailDTO.setOptCtrlCode(detail.getOptCtrlCode());
detailDTO.setKeyOper(detail.getKeyOper());
detailDTO.setActivity1Name(detail.getActivityName());
detailDTO.setOperDescription(detail.getOperDescription());
detailDTO.setEntityEntryId(detail.getEntityEntryId());
processList.add(detailDTO);
}
}
resultDTO.setProcessList(processList);
resultList.add(resultDTO);
}
// 对当前页结果进行排序先按生产令号升序再按计划开始时间升序
resultList.sort(Comparator
.comparing(OperationPlanResultDTO::getBatchNo, Comparator.nullsLast(String::compareTo))
.thenComparing(OperationPlanResultDTO::getRawPlanStartTime, Comparator.nullsLast(Date::compareTo)));
// 构造返回带 total TableDataInfo
TableDataInfo<OperationPlanResultDTO> rspData = new TableDataInfo<>();
rspData.setCode(200);
rspData.setRows(resultList);
rspData.setMsg("查询成功");
rspData.setTotal(page.getTotal());
return rspData;
}
private LambdaQueryWrapper<SfcOperationPlanningMain> buildQueryWrapper(SfcOperationPlanningMainBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SfcOperationPlanningMain> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getFid() != null, SfcOperationPlanningMain::getFid, bo.getFid());
lqw.eq(StringUtils.isNotBlank(bo.getBillNo()), SfcOperationPlanningMain::getBillNo, bo.getBillNo());
lqw.eq(StringUtils.isNotBlank(bo.getMoNumber()), SfcOperationPlanningMain::getMoNumber, bo.getMoNumber());
lqw.eq(StringUtils.isNotBlank(bo.getHbytSclh()), SfcOperationPlanningMain::getHbytSclh, bo.getHbytSclh());
lqw.eq(StringUtils.isNotBlank(bo.getProductCode()), SfcOperationPlanningMain::getProductCode, bo.getProductCode());
lqw.like(StringUtils.isNotBlank(bo.getProductName()), SfcOperationPlanningMain::getProductName, bo.getProductName());
lqw.eq(StringUtils.isNotBlank(bo.getProSpecification()), SfcOperationPlanningMain::getProSpecification, bo.getProSpecification());
lqw.eq(bo.getPlanStartTime() != null, SfcOperationPlanningMain::getPlanStartTime, bo.getPlanStartTime());
lqw.eq(bo.getPlanFinishTime() != null, SfcOperationPlanningMain::getPlanFinishTime, bo.getPlanFinishTime());
return lqw;
}
/**
* 新增工序计划主
*/
@Override
public Boolean insertByBo(SfcOperationPlanningMainBo bo) {
SfcOperationPlanningMain add = BeanUtil.toBean(bo, SfcOperationPlanningMain.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改工序计划主
*/
@Override
public Boolean updateByBo(SfcOperationPlanningMainBo bo) {
SfcOperationPlanningMain update = BeanUtil.toBean(bo, SfcOperationPlanningMain.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SfcOperationPlanningMain entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除工序计划主
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="whetherDelay" column="whether_delay"/>
<result property="delayDay" column="delay_day"/>
<result property="drawTime" column="draw_time"/>
<result property="uploadTime" column="upload_time"/>
</resultMap>
</mapper>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SfcOperationPlanningDetailMapper">
<resultMap type="com.ruoyi.system.domain.SfcOperationPlanningDetail" id="SfcOperationPlanningDetailResult">
<result property="id" column="id"/>
<result property="mainId" column="main_id"/>
<result property="fid" column="fid"/>
<result property="entityEntryId" column="entity_entry_id"/>
<result property="operNumber" column="oper_number"/>
<result property="operQty" column="oper_qty"/>
<result property="qualifiedQty" column="qualified_qty"/>
<result property="transOutQty" column="trans_out_qty"/>
<result property="unqualifiedQty" column="unqualified_qty"/>
<result property="wastageQty" column="wastage_qty"/>
<result property="transInQty" column="trans_in_qty"/>
<result property="departmentName" column="department_name"/>
<result property="workCenterName" column="work_center_name"/>
<result property="processName" column="process_name"/>
<result property="operator" column="operator"/>
<result property="operStatus" column="oper_status"/>
<result property="seqPlanStartTime" column="seq_plan_start_time"/>
<result property="seqPlanFinishTime" column="seq_plan_finish_time"/>
<result property="optCtrlCode" column="opt_ctrl_code"/>
<result property="keyOper" column="key_oper"/>
<result property="activityName" column="activity_name"/>
<result property="operDescription" column="oper_description"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
</resultMap>
</mapper>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SfcOperationPlanningMainMapper">
<resultMap type="com.ruoyi.system.domain.SfcOperationPlanningMain" id="SfcOperationPlanningMainResult">
<result property="id" column="id"/>
<result property="fid" column="fid"/>
<result property="billNo" column="bill_no"/>
<result property="moNumber" column="mo_number"/>
<result property="hbytSclh" column="hbyt_sclh"/>
<result property="productCode" column="product_code"/>
<result property="productName" column="product_name"/>
<result property="proSpecification" column="pro_specification"/>
<result property="planStartTime" column="plan_start_time"/>
<result property="planFinishTime" column="plan_finish_time"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
</resultMap>
</mapper>

View File

@ -0,0 +1,28 @@
-- ----------------------------
-- Table structure for bom_details
-- ----------------------------
DROP TABLE IF EXISTS `bom_details`;
CREATE TABLE `bom_details` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`sequence_number` int(11) DEFAULT NULL COMMENT '序号',
`drawing_number` varchar(100) DEFAULT NULL COMMENT '图号',
`name` varchar(200) DEFAULT NULL COMMENT '名称',
`quantity` decimal(10,2) DEFAULT NULL COMMENT '数量',
`material` varchar(100) DEFAULT NULL COMMENT '材料',
`unit_weight` decimal(10,3) DEFAULT NULL COMMENT '单重(kg)',
`total_weight` decimal(10,3) DEFAULT NULL COMMENT '总重(kg)',
`remarks` varchar(500) DEFAULT NULL COMMENT '备注',
`batch_quantity` int(11) DEFAULT NULL COMMENT '批次数量',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
PRIMARY KEY (`id`),
KEY `idx_drawing_number` (`drawing_number`) COMMENT '图号索引',
KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BOM物料清单明细表';
-- ----------------------------
-- 表注释
-- ----------------------------
ALTER TABLE bom_details COMMENT 'BOM物料清单明细表';