| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- å°éè´ä»·æ ¼åæå½å
¥ç°æå³çåæèåï¼ä¸ä¿®æ¹ä¸çº§èåæèªå¨æäºè§è²æéã |
| | | -- ç®æ ç¯å¢æ²¡æ /bi æ¶ï¼è¯·æç°æèåç»æéæ©ç¶èåååæ§è¡ã |
| | | SET @price_parent := (SELECT id FROM system_menu WHERE parent_id=0 AND path='/bi' AND type=1 AND deleted=b'0' LIMIT 1); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater) |
| | | SELECT 'éè´ä»·æ ¼åæ', 'erp:statistics:query', 2, 50, @price_parent, 'purchase-price', 'lucide:chart-column', 'erp/purchase/statistics/price', 'ErpPurchaseStatisticsPrice', 0, b'1', b'1', b'1', '1', '1' |
| | | WHERE @price_parent IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE component='erp/purchase/statistics/price' AND deleted=b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- å®å
¨å¶åº¦æçæ¬ä¿åï¼å¾
审æ¹çæ¬ä¸ä¼è¦çå·²æ¹åçæ¬ã |
| | | CREATE TABLE IF NOT EXISTS safety_document ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, |
| | | document_key VARCHAR(36) NOT NULL, |
| | | revision INT NOT NULL, |
| | | title VARCHAR(200) NOT NULL, |
| | | category VARCHAR(50) NOT NULL, |
| | | attachment_url VARCHAR(1000) NOT NULL, |
| | | description VARCHAR(2000) NULL, |
| | | effective_date DATE NULL, |
| | | status VARCHAR(20) NOT NULL, |
| | | owner_user_id BIGINT NOT NULL, |
| | | submitter_user_id BIGINT NULL, |
| | | submit_time DATETIME NULL, |
| | | approver_user_id BIGINT NULL, |
| | | approval_time DATETIME NULL, |
| | | approval_comment VARCHAR(500) NULL, |
| | | tenant_id BIGINT NOT NULL DEFAULT 0, |
| | | creator VARCHAR(64) DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | updater VARCHAR(64) DEFAULT '', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | deleted BIT(1) NOT NULL DEFAULT b'0', |
| | | UNIQUE KEY uk_safety_document_revision (tenant_id,document_key,revision), |
| | | KEY idx_safety_document_status (status,id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å®å
¨å¶åº¦çæ¬æ¡£æ¡'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS safety_document_approval ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, |
| | | document_id BIGINT NOT NULL, approver_user_id BIGINT NOT NULL, |
| | | decision VARCHAR(20) NOT NULL, comment VARCHAR(500) NULL, decision_time DATETIME NOT NULL, |
| | | submitter_user_id BIGINT NULL, title_snapshot VARCHAR(200) NULL, category_snapshot VARCHAR(50) NULL, |
| | | attachment_url_snapshot VARCHAR(1000) NULL, description_snapshot VARCHAR(2000) NULL, effective_date_snapshot DATE NULL, |
| | | tenant_id BIGINT NOT NULL DEFAULT 0, |
| | | creator VARCHAR(64) DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | updater VARCHAR(64) DEFAULT '', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | deleted BIT(1) NOT NULL DEFAULT b'0', KEY idx_safety_document_approval (document_id,id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å®å
¨å¶åº¦å®¡æ¹è®°å½'; |
| | | |
| | | -- ä»
å¨ç°æå®å
¨ç产ä¸çº§èåä¸å建页é¢ä¸æé®ï¼ä¸èªå¨åé
è§è²ã |
| | | -- éè¦ä¸ä¼ éä»¶çè§è²è¿åºæäºç°æ safety:file:upload æéã |
| | | SET @safety_document_parent := (SELECT id FROM system_menu WHERE parent_id=0 AND path='/safety' AND type=1 AND deleted=b'0' LIMIT 1); |
| | | INSERT INTO system_menu (name,permission,type,sort,parent_id,path,icon,component,component_name,status,visible,keep_alive,always_show,creator,updater) |
| | | SELECT 'å®å
¨å¶åº¦æ¡£æ¡','',2,35,@safety_document_parent,'document','lucide:files','safety/document/index','SafetyDocument',0,b'1',b'1',b'1','1','1' |
| | | WHERE @safety_document_parent IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE component='safety/document/index' AND deleted=b'0'); |
| | | SET @safety_document_page := (SELECT id FROM system_menu WHERE component='safety/document/index' AND deleted=b'0' LIMIT 1); |
| | | INSERT INTO system_menu (name,permission,type,sort,parent_id,path,icon,status,visible,keep_alive,always_show,creator,updater) |
| | | SELECT p.name,p.permission,3,p.sort_no,@safety_document_page,'','',0,b'1',b'1',b'1','1','1' |
| | | FROM (SELECT 'æ¥è¯¢' name,'safety:document:query' permission,1 sort_no UNION ALL |
| | | SELECT 'æ°å¢','safety:document:create',2 UNION ALL SELECT 'ä¿®æ¹åæäº¤','safety:document:update',3 UNION ALL SELECT '审æ¹','safety:document:approve',4) p |
| | | WHERE @safety_document_page IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id=@safety_document_page AND m.permission=p.permission AND m.deleted=b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 鿣忴¾ç«å
éç¥æ¨¡æ¿ã请æç®æ ç³»ç»æéé
ç½®æ¶æ¯æ¥æ¶äººè´¦å·ã |
| | | INSERT INTO system_notify_template (name,code,nickname,content,type,params,status,remark,creator,updater,deleted) |
| | | SELECT 'éæ£æ´æ¹éç¥','safety_hazard_assigned','å®å
¨ç产', |
| | | '鿣 {code}ï¼{title} 已忴¾ç»æ¨æ´æ¹ï¼æ´æ¹æéï¼{deadline}ã请å¨éæ£å°è´¦ä¸å¤çã', |
| | | 1,'["code","title","deadline"]',0,'鿣忴¾èªå¨éç¥','1','1',0 |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_notify_template WHERE code='safety_hazard_assigned' AND deleted=0); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä»
å½åéè´ä¸çº§èåå·²åç¨æ¶ï¼æå·²æä¾åºå管çç®å½å½å
¥ä»å¨ç©æµï¼ä¸å¢æ¹ä¸çº§èåã |
| | | SET @old_purchase := (SELECT id FROM system_menu WHERE parent_id=0 AND path='/purchase' AND type=1 AND status=1 AND deleted=b'0' LIMIT 1); |
| | | SET @warehouse_root := (SELECT id FROM system_menu WHERE parent_id=0 AND path='/wls' AND type=1 AND status=0 AND deleted=b'0' LIMIT 1); |
| | | SET @srm_menu := (SELECT id FROM system_menu WHERE parent_id=@old_purchase AND path='srm' AND type=1 AND deleted=b'0' LIMIT 1); |
| | | SET @srm_conflict := (SELECT COUNT(*) FROM system_menu WHERE parent_id=@warehouse_root AND path='srm' AND deleted=b'0' AND id<>IFNULL(@srm_menu,0)); |
| | | UPDATE system_menu SET parent_id=@warehouse_root, updater='1', update_time=NOW() |
| | | WHERE id=@srm_menu AND @warehouse_root IS NOT NULL AND @srm_conflict=0; |
| | | -- ä¿çåæææè§è²å¯¹æ°å¢ç¥å
èåçå¯è§æ§ï¼ä¸èµäºé¢å¤ä¸å¡æé®æéã |
| | | INSERT INTO system_role_menu (role_id,menu_id,creator,updater,tenant_id) |
| | | SELECT DISTINCT rm.role_id,@warehouse_root,'1','1',rm.tenant_id FROM system_role_menu rm |
| | | WHERE rm.menu_id=@srm_menu AND rm.deleted=b'0' AND @warehouse_root IS NOT NULL AND @srm_conflict=0 |
| | | AND NOT EXISTS (SELECT 1 FROM system_role_menu existing WHERE existing.role_id=rm.role_id AND existing.tenant_id=rm.tenant_id |
| | | AND existing.menu_id=@warehouse_root AND existing.deleted=b'0'); |
| | |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceRespVO; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | |
| | | @Resource |
| | | private ErpPurchaseStatisticsService purchaseStatisticsService; |
| | | |
| | | @GetMapping("/price") |
| | | @Operation(summary = "éè´ä»·æ ¼åæ") |
| | | @PreAuthorize("@ss.hasPermission('erp:statistics:query')") |
| | | public CommonResult<PageResult<ErpPurchasePriceRespVO>> getPriceAnalysis(@Validated PageParam page, |
| | | @RequestParam(value = "productId", required = false) Long productId, |
| | | @RequestParam(value = "orderTime", required = false) |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime[] orderTime) { |
| | | return success(purchaseStatisticsService.getPriceAnalysis(page, productId, orderTime)); |
| | | } |
| | | |
| | | @GetMapping("/summary") |
| | | @Operation(summary = "è·å¾éè´ç»è®¡") |
| | | @PreAuthorize("@ss.hasPermission('erp:statistics:query')") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase; |
| | | |
| | | import lombok.Data; |
| | | import java.math.BigDecimal; |
| | | |
| | | @Data |
| | | public class ErpPurchasePriceRespVO { |
| | | private Long productId; |
| | | private String productName; |
| | | private String productBarCode; |
| | | private BigDecimal totalQuantity; |
| | | private BigDecimal avgPrice; |
| | | private BigDecimal minPrice; |
| | | private BigDecimal maxPrice; |
| | | private BigDecimal latestPrice; |
| | | private BigDecimal previousPrice; |
| | | private BigDecimal priceChangeRate; |
| | | } |
| | |
| | | |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceRespVO; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | |
| | | BigDecimal getPurchasePrice(@Param("beginTime") LocalDateTime beginTime, |
| | | @Param("endTime") LocalDateTime endTime); |
| | | |
| | | IPage<ErpPurchasePriceRespVO> selectPriceAnalysis(IPage<ErpPurchasePriceRespVO> page, |
| | | @Param("productId") Long productId, @Param("beginTime") LocalDateTime beginTime, |
| | | @Param("endTime") LocalDateTime endTime); |
| | | |
| | | } |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceRespVO; |
| | | |
| | | /** |
| | | * ERP éè´ç»è®¡ Service æ¥å£ |
| | |
| | | */ |
| | | BigDecimal getPurchasePrice(LocalDateTime beginTime, LocalDateTime endTime); |
| | | |
| | | PageResult<ErpPurchasePriceRespVO> getPriceAnalysis(PageParam page, Long productId, LocalDateTime[] orderTime); |
| | | |
| | | } |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceRespVO; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | |
| | | /** |
| | | * ERP éè´ç»è®¡ Service å®ç°ç±» |
| | |
| | | return purchaseStatisticsMapper.getPurchasePrice(beginTime, endTime); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<ErpPurchasePriceRespVO> getPriceAnalysis(PageParam page, Long productId, LocalDateTime[] orderTime) { |
| | | LocalDateTime begin = orderTime != null && orderTime.length > 0 ? orderTime[0] : null; |
| | | LocalDateTime end = orderTime != null && orderTime.length > 1 ? orderTime[1] : null; |
| | | var result = purchaseStatisticsMapper.selectPriceAnalysis(new Page<>(page.getPageNo(), page.getPageSize()), productId, begin, end); |
| | | result.getRecords().forEach(row -> row.setPriceChangeRate(PurchasePriceMetrics.changeRate(row.getLatestPrice(), row.getPreviousPrice()))); |
| | | return new PageResult<>(result.getRecords(), result.getTotal()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.erp.service.statistics; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | final class PurchasePriceMetrics { |
| | | private PurchasePriceMetrics() {} |
| | | |
| | | static BigDecimal changeRate(BigDecimal latest, BigDecimal previous) { |
| | | if (latest == null || previous == null || previous.signum() == 0) return null; |
| | | return latest.subtract(previous).multiply(BigDecimal.valueOf(100)) |
| | | .divide(previous, 2, RoundingMode.HALF_UP); |
| | | } |
| | | } |
| | |
| | | AND deleted = 0) |
| | | </select> |
| | | |
| | | </mapper> |
| | | <select id="selectPriceAnalysis" resultType="cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceRespVO"> |
| | | SELECT ranked.product_id, MAX(m.name) AS product_name, MAX(m.code) AS product_bar_code, |
| | | SUM(ranked.quantity) AS total_quantity, |
| | | ROUND(SUM(ranked.product_price * ranked.quantity) / NULLIF(SUM(ranked.quantity), 0), 4) AS avg_price, |
| | | MIN(ranked.product_price) AS min_price, MAX(ranked.product_price) AS max_price, |
| | | MAX(CASE WHEN ranked.row_num = 1 THEN ranked.product_price END) AS latest_price, |
| | | MAX(CASE WHEN ranked.row_num = 2 THEN ranked.product_price END) AS previous_price |
| | | FROM ( |
| | | SELECT i.product_id, i.product_price, i.count AS quantity, |
| | | ROW_NUMBER() OVER (PARTITION BY i.product_id ORDER BY o.order_time DESC, o.id DESC, i.id DESC) AS row_num |
| | | FROM erp_purchase_order_items i JOIN erp_purchase_order o ON o.id = i.order_id |
| | | WHERE i.deleted = 0 AND o.deleted = 0 AND o.status = 20 |
| | | AND i.product_id IS NOT NULL AND i.product_price IS NOT NULL AND i.count > 0 |
| | | <if test="productId != null">AND i.product_id = #{productId}</if> |
| | | <if test="beginTime != null">AND o.order_time >= #{beginTime}</if> |
| | | <if test="endTime != null">AND o.order_time <= #{endTime}</if> |
| | | ) ranked LEFT JOIN mdm_item m ON m.id = ranked.product_id AND m.deleted = 0 |
| | | GROUP BY ranked.product_id ORDER BY ranked.product_id |
| | | </select> |
| | | </mapper> |
| | |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | import java.time.LocalDateTime; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | |
| | | @Resource |
| | | private MesCollectQueryService queryService; |
| | | |
| | | @GetMapping("/offline/intervals") |
| | | @Operation(summary = "æ¥è¯¢æ°éç¦»çº¿åæºåºé´") |
| | | @PreAuthorize("@ss.hasPermission('mes:collect:query')") |
| | | public CommonResult<MesCollectOfflineResultRespVO> getOfflineIntervals( |
| | | @RequestParam("deviceCode") String deviceCode, |
| | | @RequestParam("startTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, |
| | | @RequestParam("endTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) { |
| | | return success(queryService.getOfflineIntervals(deviceCode, startTime, endTime)); |
| | | } |
| | | |
| | | @GetMapping("/device/list") |
| | | @Operation(summary = "è·åæ°é设å¤ç¶æå表") |
| | | @PreAuthorize("@ss.hasPermission('mes:collect:query')") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.collect.vo; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | | public class MesCollectOfflineIntervalRespVO { |
| | | private LocalDateTime startTime; |
| | | private LocalDateTime endTime; |
| | | private long durationSeconds; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.collect.vo; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | | public class MesCollectOfflineResultRespVO { |
| | | private LocalDateTime firstSampleTime; |
| | | private List<MesCollectOfflineIntervalRespVO> intervals; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document.SafetyDocumentApprovalReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document.SafetyDocumentSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentApprovalDO; |
| | | import cn.iocoder.yudao.module.mes.service.safety.SafetyDocumentService; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @RestController |
| | | @RequestMapping("/safety/document") |
| | | @Validated |
| | | public class SafetyDocumentController { |
| | | @Resource private SafetyDocumentService service; |
| | | |
| | | @GetMapping("/page") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:query')") |
| | | public CommonResult<PageResult<SafetyDocumentDO>> page(@Valid PageParam page, |
| | | @RequestParam(value = "title", required = false) String title, |
| | | @RequestParam(value = "status", required = false) String status) { |
| | | return success(service.page(page, title, status)); |
| | | } |
| | | @GetMapping("/approvals") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:query')") |
| | | public CommonResult<java.util.List<SafetyDocumentApprovalDO>> approvals(@RequestParam("id") Long id) { return success(service.approvals(id)); } |
| | | @PostMapping("/create") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:create')") |
| | | public CommonResult<Long> create(@Valid @RequestBody SafetyDocumentSaveReqVO req) { return success(service.create(req)); } |
| | | |
| | | @PutMapping("/update") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:update')") |
| | | public CommonResult<Boolean> update(@Valid @RequestBody SafetyDocumentSaveReqVO req) { service.update(req); return success(true); } |
| | | |
| | | @PutMapping("/submit") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:update')") |
| | | public CommonResult<Boolean> submit(@RequestParam("id") Long id) { service.submit(id); return success(true); } |
| | | |
| | | @PutMapping("/approve") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:approve')") |
| | | public CommonResult<Boolean> approve(@Valid @RequestBody SafetyDocumentApprovalReqVO req) { service.approve(req); return success(true); } |
| | | |
| | | @PostMapping("/revision") |
| | | @PreAuthorize("@ss.hasPermission('safety:document:create')") |
| | | public CommonResult<Long> revision(@RequestParam("id") Long id) { return success(service.createRevision(id)); } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document; |
| | | |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class SafetyDocumentApprovalReqVO { |
| | | @NotNull private Long id; |
| | | @NotNull private Boolean approved; |
| | | @Size(max = 500) private String comment; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | public class SafetyDocumentSaveReqVO { |
| | | private Long id; |
| | | @NotBlank @Size(max = 200) private String title; |
| | | @NotBlank @Size(max = 50) private String category; |
| | | @NotBlank @Size(max = 1000) private String attachmentUrl; |
| | | @Size(max = 2000) private String description; |
| | | private LocalDate effectiveDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import java.time.LocalDateTime; |
| | | import java.time.LocalDate; |
| | | |
| | | @TableName("safety_document_approval") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class SafetyDocumentApprovalDO extends BaseDO { |
| | | @TableId private Long id; |
| | | private Long documentId; |
| | | private Long approverUserId; |
| | | private String decision; |
| | | private String comment; |
| | | private LocalDateTime decisionTime; |
| | | private Long submitterUserId; |
| | | private String titleSnapshot; |
| | | private String categorySnapshot; |
| | | private String attachmentUrlSnapshot; |
| | | private String descriptionSnapshot; |
| | | private LocalDate effectiveDateSnapshot; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.FieldStrategy; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @TableName("safety_document") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class SafetyDocumentDO extends BaseDO { |
| | | @TableId private Long id; |
| | | private String documentKey; |
| | | private Integer revision; |
| | | private String title; |
| | | private String category; |
| | | private String attachmentUrl; |
| | | @TableField(updateStrategy = FieldStrategy.ALWAYS) private String description; |
| | | @TableField(updateStrategy = FieldStrategy.ALWAYS) private LocalDate effectiveDate; |
| | | private String status; |
| | | private Long ownerUserId; |
| | | private Long submitterUserId; |
| | | private LocalDateTime submitTime; |
| | | private Long approverUserId; |
| | | private LocalDateTime approvalTime; |
| | | private String approvalComment; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentApprovalDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface SafetyDocumentApprovalMapper extends BaseMapperX<SafetyDocumentApprovalDO> {} |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface SafetyDocumentMapper extends BaseMapperX<SafetyDocumentDO> { |
| | | default SafetyDocumentDO lockById(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<SafetyDocumentDO>().eq(SafetyDocumentDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | default SafetyDocumentDO latest(String documentKey) { |
| | | return selectOne(new LambdaQueryWrapperX<SafetyDocumentDO>().eq(SafetyDocumentDO::getDocumentKey, documentKey) |
| | | .orderByDesc(SafetyDocumentDO::getRevision).last("LIMIT 1")); |
| | | } |
| | | } |
| | |
| | | @Mapper |
| | | public interface SafetyHazardMapper extends BaseMapperX<SafetyHazardDO> { |
| | | |
| | | default SafetyHazardDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<SafetyHazardDO>().eq(SafetyHazardDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | |
| | | default PageResult<SafetyHazardDO> selectPage(SafetyHazardPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<SafetyHazardDO>() |
| | | .likeIfPresent(SafetyHazardDO::getCode, reqVO.getCode()) |
| | |
| | | ErrorCode SAFETY_HAZARD_STATUS_ERROR = new ErrorCode(1_040_964_001, "å½åéæ£ç¶æä¸å
许该æä½"); |
| | | ErrorCode SAFETY_HAZARD_ASSIGNEE_REQUIRED = new ErrorCode(1_040_964_002, "åæ´¾å¿
é¡»æå®æ´æ¹è´£ä»»äºº"); |
| | | ErrorCode SAFETY_HAZARD_SAME_OPERATOR = new ErrorCode(1_040_964_003, "æ´æ¹äººä¸å¤æ¥äººä¸è½æ¯åä¸äºº"); |
| | | ErrorCode SAFETY_HAZARD_ASSIGNEE_NO_ACCOUNT = new ErrorCode(1_040_964_004, "æ´æ¹è´£ä»»äººæªå
³èç³»ç»è´¦å·ï¼æ æ³åéæ´æ¹éç¥"); |
| | | ErrorCode SAFETY_HAZARD_NOTICE_UNAVAILABLE = new ErrorCode(1_040_964_005, "æ´æ¹éç¥æ¨¡æ¿æªå¯ç¨ï¼è¯·è系管çåå¤ç"); |
| | | |
| | | // ========== é«å±ä½ä¸ç¥¨ 1_040_965_xxx ========== |
| | | ErrorCode SAFETY_PERMIT_NOT_EXISTS = new ErrorCode(1_040_965_000, "ä½ä¸ç¥¨ä¸åå¨"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.collect; |
| | | |
| | | import java.time.Duration; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** æ ¹æ®éæ ·é´éæ¨ç®ç¦»çº¿åºé´ï¼ä¸æ¨æé¦æ¬¡éæ ·ä¹åçç¶æã */ |
| | | final class CollectOfflinePolicy { |
| | | record Interval(LocalDateTime start, LocalDateTime end) {} |
| | | |
| | | private CollectOfflinePolicy() {} |
| | | |
| | | static List<Interval> calculate(List<LocalDateTime> samples, LocalDateTime start, |
| | | LocalDateTime end, Duration timeout) { |
| | | if (start == null || end == null || !start.isBefore(end) || timeout == null || timeout.isNegative() || timeout.isZero()) { |
| | | throw new IllegalArgumentException("æ¶é´èå´æç¦»çº¿é弿 æ"); |
| | | } |
| | | List<LocalDateTime> ordered = samples.stream().filter(t -> t != null && !t.isAfter(end)).distinct().sorted().toList(); |
| | | List<Interval> result = new ArrayList<>(); |
| | | for (int i = 0; i < ordered.size(); i++) { |
| | | LocalDateTime offline = ordered.get(i).plus(timeout); |
| | | LocalDateTime recovery = i + 1 < ordered.size() ? ordered.get(i + 1) : end; |
| | | LocalDateTime clippedStart = offline.isBefore(start) ? start : offline; |
| | | LocalDateTime clippedEnd = recovery.isAfter(end) ? end : recovery; |
| | | if (clippedStart.isBefore(clippedEnd)) result.add(new Interval(clippedStart, clippedEnd)); |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.framework.collect.config.MesCollectProperties; |
| | | import java.time.Duration; |
| | | import java.util.ArrayList; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | |
| | | private MesCollectTaskResolver taskResolver; |
| | | @Resource |
| | | private MesMdWorkstationService workstationService; |
| | | @Resource |
| | | private MesCollectProperties collectProperties; |
| | | |
| | | /** åºäºä¿ççæ°éåå²è®¡ç®ç¦»çº¿åºé´ï¼é¦æ¬¡éæ ·ä¹åä¸ä½æ¨æã */ |
| | | public MesCollectOfflineResultRespVO getOfflineIntervals(String deviceCode, LocalDateTime start, LocalDateTime end) { |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (deviceCode == null || deviceCode.isBlank() || start == null || end == null || !start.isBefore(end) |
| | | || end.isAfter(now) || Duration.between(start, end).compareTo(Duration.ofDays(7)) > 0) { |
| | | throw invalidParamException("è¯·éæ©ææç设å¤åæ¶é´èå´ï¼å次æå¤æ¥è¯¢ä¸å¤©ï¼ç»ææ¶é´ä¸è½æäºå½åæ¶é´"); |
| | | } |
| | | if (start.isBefore(now.minusDays(collectProperties.getRetention().getRealtimeDays()))) { |
| | | throw invalidParamException("æ¥è¯¢æ¶é´è¶
åºæ°éåå²ä¿çæï¼æ æ³å¯é 计ç®åæºè®°å½"); |
| | | } |
| | | var prior = realtimeHistoryMapper.selectOne(new LambdaQueryWrapperX<MesCollectRealtimeHistoryDO>() |
| | | .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) |
| | | .ge(MesCollectRealtimeHistoryDO::getCollectTime, now.minusDays(collectProperties.getRetention().getRealtimeDays())) |
| | | .le(MesCollectRealtimeHistoryDO::getCollectTime, start) |
| | | .orderByDesc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 1")); |
| | | var history = realtimeHistoryMapper.selectList(new LambdaQueryWrapperX<MesCollectRealtimeHistoryDO>() |
| | | .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) |
| | | .gt(MesCollectRealtimeHistoryDO::getCollectTime, start) |
| | | .le(MesCollectRealtimeHistoryDO::getCollectTime, end) |
| | | .orderByAsc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 100001")); |
| | | if (history.size() > 100000) throw invalidParamException("éæ ·æ°æ®è¿å¤ï¼è¯·ç¼©çæ¥è¯¢æ¶é´èå´"); |
| | | List<LocalDateTime> samples = new ArrayList<>(); |
| | | if (prior != null) samples.add(prior.getCollectTime()); |
| | | history.forEach(item -> samples.add(item.getCollectTime())); |
| | | var intervals = CollectOfflinePolicy.calculate(samples, start, end, Duration.ofMinutes(ONLINE_TIMEOUT_MINUTES)).stream() |
| | | .map(item -> new MesCollectOfflineIntervalRespVO(item.start(), item.end(), Duration.between(item.start(), item.end()).getSeconds())) |
| | | .toList(); |
| | | return new MesCollectOfflineResultRespVO(samples.isEmpty() ? null : samples.getFirst(), intervals); |
| | | } |
| | | |
| | | /** |
| | | * è·åå·²é
ç½®æ°éç¼ç ç设å¤å®æ¶ç¶æå表 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.safety; |
| | | |
| | | import java.util.Objects; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; |
| | | |
| | | final class SafetyDocumentPolicy { |
| | | private SafetyDocumentPolicy() {} |
| | | static void checkApproval(String status, Long submitterId, Long approverId) { |
| | | if (!"PENDING".equals(status)) throw invalidParamException("ä»
å¾
审æ¹çå¶åº¦å¯ä»¥å®¡æ¹"); |
| | | if (submitterId == null || approverId == null || Objects.equals(submitterId, approverId)) { |
| | | throw invalidParamException("审æ¹äººä¸è½æ¯å¶åº¦æäº¤äºº"); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.safety; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document.SafetyDocumentApprovalReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.document.SafetyDocumentSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyDocumentMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyDocumentApprovalMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDocumentApprovalDO; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.time.LocalDateTime; |
| | | import java.time.LocalDate; |
| | | import java.util.Objects; |
| | | import java.util.UUID; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Service |
| | | public class SafetyDocumentService { |
| | | @Resource private SafetyDocumentMapper mapper; |
| | | @Resource private SafetyDocumentApprovalMapper approvalMapper; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long create(SafetyDocumentSaveReqVO req) { |
| | | var document = new SafetyDocumentDO(); |
| | | document.setDocumentKey(UUID.randomUUID().toString()); |
| | | document.setRevision(1); |
| | | document.setStatus("DRAFT"); |
| | | document.setOwnerUserId(currentUser()); |
| | | apply(document, req); |
| | | mapper.insert(document); |
| | | return document.getId(); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void update(SafetyDocumentSaveReqVO req) { |
| | | if (req.getId() == null) throw invalidParamException("è¯·éæ©å¶åº¦æ¡£æ¡"); |
| | | var document = requireLocked(req.getId()); |
| | | if (!"DRAFT".equals(document.getStatus()) && !"REJECTED".equals(document.getStatus())) { |
| | | throw invalidParamException("åªæè稿æé©³åçå¶åº¦å¯ä»¥ä¿®æ¹"); |
| | | } |
| | | if (!Objects.equals(document.getOwnerUserId(), currentUser())) throw invalidParamException("åªæå建人å¯ä»¥ä¿®æ¹å¶åº¦è稿"); |
| | | apply(document, req); |
| | | document.setStatus("DRAFT"); |
| | | mapper.updateById(document); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void submit(Long id) { |
| | | var document = requireLocked(id); |
| | | if (!"DRAFT".equals(document.getStatus())) throw invalidParamException("åªæè稿å¯ä»¥æäº¤å®¡æ¹"); |
| | | if (!Objects.equals(document.getOwnerUserId(), currentUser())) throw invalidParamException("åªæå建人å¯ä»¥æäº¤å¶åº¦"); |
| | | document.setStatus("PENDING"); |
| | | document.setSubmitterUserId(currentUser()); |
| | | document.setSubmitTime(LocalDateTime.now()); |
| | | mapper.updateById(document); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void approve(SafetyDocumentApprovalReqVO req) { |
| | | var document = requireLocked(req.getId()); |
| | | SafetyDocumentPolicy.checkApproval(document.getStatus(), document.getSubmitterUserId(), currentUser()); |
| | | if (!req.getApproved() && (req.getComment() == null || req.getComment().isBlank())) throw invalidParamException("é©³åæ¶è¯·å¡«åå®¡æ¹æè§"); |
| | | if (req.getApproved()) { |
| | | if (document.getEffectiveDate() != null && document.getEffectiveDate().isAfter(LocalDate.now())) { |
| | | throw invalidParamException("æä¸æ¯ææªæ¥æ¥æèªå¨çæï¼è¯·å¨çææ¥æå½å¤©å®¡æ¹æä¿®æ¹çææ¥æ"); |
| | | } |
| | | mapper.update(null, new LambdaUpdateWrapper<SafetyDocumentDO>() |
| | | .eq(SafetyDocumentDO::getDocumentKey, document.getDocumentKey()) |
| | | .eq(SafetyDocumentDO::getStatus, "APPROVED") |
| | | .ne(SafetyDocumentDO::getId, document.getId()) |
| | | .set(SafetyDocumentDO::getStatus, "SUPERSEDED")); |
| | | } |
| | | document.setStatus(req.getApproved() ? "APPROVED" : "REJECTED"); |
| | | document.setApproverUserId(currentUser()); |
| | | document.setApprovalTime(LocalDateTime.now()); |
| | | document.setApprovalComment(req.getComment()); |
| | | mapper.updateById(document); |
| | | var log = new SafetyDocumentApprovalDO(); |
| | | log.setDocumentId(document.getId()); log.setApproverUserId(currentUser()); |
| | | log.setDecision(document.getStatus()); log.setComment(req.getComment()); log.setDecisionTime(document.getApprovalTime()); |
| | | log.setSubmitterUserId(document.getSubmitterUserId()); log.setTitleSnapshot(document.getTitle()); |
| | | log.setCategorySnapshot(document.getCategory()); log.setAttachmentUrlSnapshot(document.getAttachmentUrl()); |
| | | log.setDescriptionSnapshot(document.getDescription()); |
| | | log.setEffectiveDateSnapshot(document.getEffectiveDate()); |
| | | approvalMapper.insert(log); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createRevision(Long id) { |
| | | var previous = requireLocked(id); |
| | | if (!"APPROVED".equals(previous.getStatus())) throw invalidParamException("åªæå·²æ¹åçå¶åº¦å¯ä»¥å建æ°çæ¬"); |
| | | var latest = mapper.latest(previous.getDocumentKey()); |
| | | if (latest == null || !Objects.equals(latest.getId(), previous.getId())) throw invalidParamException("请åºäºææ°çæ¬å建修订ï¼å·²æåç»çæ¬"); |
| | | var next = new SafetyDocumentDO(); |
| | | next.setDocumentKey(previous.getDocumentKey()); |
| | | next.setRevision(previous.getRevision() + 1); |
| | | next.setTitle(previous.getTitle()); next.setCategory(previous.getCategory()); |
| | | next.setAttachmentUrl(previous.getAttachmentUrl()); next.setDescription(previous.getDescription()); |
| | | next.setEffectiveDate(previous.getEffectiveDate()); |
| | | next.setStatus("DRAFT"); next.setOwnerUserId(currentUser()); |
| | | mapper.insert(next); |
| | | return next.getId(); |
| | | } |
| | | |
| | | public PageResult<SafetyDocumentDO> page(PageParam page, String title, String status) { |
| | | return mapper.selectPage(page, new LambdaQueryWrapperX<SafetyDocumentDO>() |
| | | .likeIfPresent(SafetyDocumentDO::getTitle, title).eqIfPresent(SafetyDocumentDO::getStatus, status) |
| | | .orderByDesc(SafetyDocumentDO::getId)); |
| | | } |
| | | |
| | | public java.util.List<SafetyDocumentApprovalDO> approvals(Long id) { |
| | | if (id == null || mapper.selectById(id) == null) throw invalidParamException("å¶åº¦æ¡£æ¡ä¸åå¨"); |
| | | return approvalMapper.selectList(new LambdaQueryWrapperX<SafetyDocumentApprovalDO>() |
| | | .eq(SafetyDocumentApprovalDO::getDocumentId, id).orderByDesc(SafetyDocumentApprovalDO::getId)); |
| | | } |
| | | |
| | | private SafetyDocumentDO requireLocked(Long id) { |
| | | if (id == null) throw invalidParamException("è¯·éæ©å¶åº¦æ¡£æ¡"); |
| | | var result = mapper.lockById(id); |
| | | if (result == null) throw invalidParamException("å¶åº¦æ¡£æ¡ä¸åå¨"); |
| | | return result; |
| | | } |
| | | private Long currentUser() { |
| | | Long userId = getLoginUserId(); |
| | | if (userId == null) throw invalidParamException("ç»å½ç¶æå·²å¤±æ"); |
| | | return userId; |
| | | } |
| | | private void apply(SafetyDocumentDO doc, SafetyDocumentSaveReqVO req) { |
| | | doc.setTitle(req.getTitle().trim()); doc.setCategory(req.getCategory().trim()); |
| | | doc.setAttachmentUrl(req.getAttachmentUrl().trim()); doc.setDescription(req.getDescription()); |
| | | doc.setEffectiveDate(req.getEffectiveDate()); |
| | | } |
| | | } |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; |
| | | import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*; |
| | |
| | | private SafetyHazardMapper hazardMapper; |
| | | @Resource |
| | | private SafetyRefResolver refResolver; |
| | | @Resource |
| | | private NotifyMessageSendApi notifyMessageSendApi; |
| | | @Resource |
| | | private AdminUserService adminUserService; |
| | | |
| | | /** |
| | | * 鿣䏿¥ï¼å«ç§»å¨ç«¯æç
§ä¸ä¼ ç photoUrlsï¼ |
| | |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void assignHazard(SafetyHazardAssignReqVO reqVO) { |
| | | SafetyHazardDO hazard = validateHazardExists(reqVO.getId()); |
| | | SafetyHazardDO hazard = hazardMapper.selectByIdForUpdate(reqVO.getId()); |
| | | if (hazard == null) throw exception(SAFETY_HAZARD_NOT_EXISTS); |
| | | if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(hazard.getStatus()) |
| | | && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) { |
| | | throw exception(SAFETY_HAZARD_STATUS_ERROR); |
| | |
| | | throw exception(SAFETY_HAZARD_ASSIGNEE_REQUIRED); |
| | | } |
| | | var assignee = refResolver.requireEmployee(reqVO.getAssigneeId(), SAFETY_HAZARD_ASSIGNEE_REQUIRED); |
| | | if (assignee.getUserId() == null) throw exception(SAFETY_HAZARD_ASSIGNEE_NO_ACCOUNT); |
| | | adminUserService.validateUserList(List.of(assignee.getUserId())); |
| | | SafetyHazardDO update = new SafetyHazardDO(); |
| | | update.setId(hazard.getId()); |
| | | update.setAssigneeId(assignee.getId()); |
| | |
| | | update.setRemark(reqVO.getRemark().trim()); |
| | | } |
| | | hazardMapper.updateById(update); |
| | | NotifySendSingleToUserReqDTO notice = new NotifySendSingleToUserReqDTO(); |
| | | notice.setUserId(assignee.getUserId()); |
| | | notice.setTemplateCode("safety_hazard_assigned"); |
| | | notice.setTemplateParams(Map.of("code", hazard.getCode(), "title", hazard.getTitle(), |
| | | "deadline", reqVO.getRectifyDeadline() == null ? "æªè®¾ç½®" : reqVO.getRectifyDeadline().toString())); |
| | | if (notifyMessageSendApi.sendSingleMessageToAdmin(notice) == null) { |
| | | throw exception(SAFETY_HAZARD_NOTICE_UNAVAILABLE); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.DecimalMin; |
| | | import jakarta.validation.constraints.DecimalMax; |
| | | import jakarta.validation.constraints.Min; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | |
| | | private Long supplierId; |
| | | |
| | | @Schema(description = "ææ ID", example = "1") |
| | | @NotNull(message = "ææ IDä¸è½ä¸ºç©º") |
| | | private Long bidId; |
| | | |
| | | @Schema(description = "ææ ç©æID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | |
| | | |
| | | @Schema(description = "æ¥ä»·ä»·æ ¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000") |
| | | @NotNull(message = "æ¥ä»·ä»·æ ¼ä¸è½ä¸ºç©º") |
| | | @DecimalMin(value = "0", message = "æ¥ä»·ä¸è½ä¸ºè´æ°") |
| | | private BigDecimal quotePrice; |
| | | |
| | | @Schema(description = "ç¨ç", example = "13") |
| | | @DecimalMin(value = "0", message = "ç¨çä¸è½ä¸ºè´æ°") |
| | | @DecimalMax(value = "100", message = "ç¨çä¸è½è¶
è¿100%") |
| | | private BigDecimal taxRate; |
| | | |
| | | @Schema(description = "äº¤è´§å¨æï¼å¤©ï¼", example = "30") |
| | | private Integer deliveryCycle; |
| | | @Min(value = 0, message = "äº¤è´§å¨æä¸è½ä¸ºè´æ°") private Integer deliveryCycle; |
| | | |
| | | @Schema(description = "仿¬¾æ¡ä»¶", example = "è´§å°30天仿¬¾") |
| | | private String paymentTerms; |
| | | |
| | | @Schema(description = "è´¨ä¿æï¼æï¼", example = "12") |
| | | private Integer warrantyPeriod; |
| | | @Min(value = 0, message = "è´¨ä¿æä¸è½ä¸ºè´æ°") private Integer warrantyPeriod; |
| | | |
| | | @Schema(description = "夿³¨", example = "å«è¿è´¹") |
| | | private String remark; |
| | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderConfigDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | |
| | | @Mapper |
| | | public interface SrmTenderConfigMapper extends BaseMapperX<SrmTenderConfigDO> { |
| | | default SrmTenderConfigDO selectLatest() { |
| | | return selectOne(new LambdaQueryWrapperX<SrmTenderConfigDO>() |
| | | .orderByDesc(SrmTenderConfigDO::getUpdateTime) |
| | | .orderByDesc(SrmTenderConfigDO::getId).last("LIMIT 1")); |
| | | } |
| | | } |
| | |
| | | |
| | | // ========== éç¨ ========== |
| | | ErrorCode QUOTE_MATERIAL_DUPLICATE = new ErrorCode(1_040_900_001, "è¯¥ææ å·²åå¨ç¸åç©æçæ¥ä»·"); |
| | | ErrorCode QUOTE_MATERIAL_NOT_IN_PROJECT = new ErrorCode(1_040_900_002, "æ¥ä»·ç©æä¸å±äºå½åææ é¡¹ç®"); |
| | | ErrorCode QUOTE_NOT_EXISTS = new ErrorCode(1_040_900_003, "æ¥ä»·è®°å½ä¸åå¨"); |
| | | ErrorCode QUOTE_BID_MISMATCH = new ErrorCode(1_040_900_004, "æ¥ä»·è®°å½ä¸å±äºå½åææ "); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierQuoteMapper; |
| | | import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper; |
| | | import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper; |
| | | import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderMaterialMapper; |
| | | import cn.iocoder.yudao.module.srm.enums.BidStatusEnum; |
| | | import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants; |
| | | import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*; |
| | | |
| | |
| | | |
| | | @Resource |
| | | private SrmSupplierMapper supplierMapper; |
| | | @Resource |
| | | private SrmTenderMaterialMapper tenderMaterialMapper; |
| | | |
| | | // ========== ææ ç®¡ç ========== |
| | | |
| | |
| | | if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) { |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN); |
| | | } |
| | | validateQuoteMaterial(createReqVO.getTenderMaterialId(), bid.getTenderProjectId()); |
| | | // æ ¡éªå䏿æ ä¸ç©æä¸å¯é夿¥ä»· |
| | | if (supplierQuoteMapper.existsByBidAndMaterial(createReqVO.getBidId(), createReqVO.getTenderMaterialId())) { |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE); |
| | |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN); |
| | | } |
| | | // æ ¡éªå䏿æ ä¸ç©æä¸å¯é夿¥ä»·ï¼æé¤èªèº«ï¼ |
| | | SrmSupplierQuoteDO existing = supplierQuoteMapper.selectById(updateReqVO.getId()); |
| | | if (existing != null && !existing.getTenderMaterialId().equals(updateReqVO.getTenderMaterialId())) { |
| | | SrmSupplierQuoteDO existing = updateReqVO.getId() == null ? null : supplierQuoteMapper.selectById(updateReqVO.getId()); |
| | | if (existing == null) throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_NOT_EXISTS); |
| | | if (!Objects.equals(existing.getBidId(), bid.getId())) throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_BID_MISMATCH); |
| | | validateQuoteMaterial(updateReqVO.getTenderMaterialId(), bid.getTenderProjectId()); |
| | | if (!Objects.equals(existing.getTenderMaterialId(), updateReqVO.getTenderMaterialId())) { |
| | | if (supplierQuoteMapper.existsByBidAndMaterial(updateReqVO.getBidId(), updateReqVO.getTenderMaterialId())) { |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE); |
| | | } |
| | | } |
| | | SrmSupplierQuoteDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierQuoteDO.class); |
| | | updateObj.setTenderProjectId(bid.getTenderProjectId()); |
| | | updateObj.setSupplierId(bid.getSupplierId()); |
| | | supplierQuoteMapper.updateById(updateObj); |
| | | } |
| | | |
| | | private void validateQuoteMaterial(Long materialId, Long projectId) { |
| | | var material = materialId == null ? null : tenderMaterialMapper.selectById(materialId); |
| | | if (material == null || !Objects.equals(material.getTenderProjectId(), projectId)) { |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_NOT_IN_PROJECT); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<SrmSupplierQuoteDO> getQuoteList(Long bidId) { |
| | | return supplierQuoteMapper.selectListByBidId(bidId); |
| | |
| | | |
| | | @Override |
| | | public SrmTenderConfigDO getConfig() { |
| | | SrmTenderConfigDO config = srmTenderConfigMapper.selectOne(null); |
| | | SrmTenderConfigDO config = srmTenderConfigMapper.selectLatest(); |
| | | if (config == null) { |
| | | config = new SrmTenderConfigDO(); |
| | | config.setDefaultBiddingMode(BiddingModeEnum.FULL.getCode()); |
| | |
| | | |
| | | @Override |
| | | public void saveConfig(String defaultBiddingMode) { |
| | | SrmTenderConfigDO config = srmTenderConfigMapper.selectOne(null); |
| | | SrmTenderConfigDO config = srmTenderConfigMapper.selectLatest(); |
| | | if (config != null) { |
| | | config.setDefaultBiddingMode(defaultBiddingMode); |
| | | srmTenderConfigMapper.updateById(config); |