已添加7个文件
已修改18个文件
741 ■■■■ 文件已修改
src/main/java/com/ruoyi/CodeGenerator.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysNotice.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/ISysNoticeService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 139 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceLocationConfigMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceLocationConfigServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 111 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/LocationUtils.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceLocationConfigMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/CodeGenerator.java
@@ -19,11 +19,11 @@
// æ¼”示例子,执行 main æ–¹æ³•控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-new";
    public static String database_url = "jdbc:mysql://1.15.17.182:9999/product-inventory-management-new";
    public static String database_username = "root";
    public static String database_password= "123456";
    public static String database_password= "xd@123456..";
    public static String author = "芯导软件(江苏)有限公司";
    public static String model = "purchase"; // æ¨¡å—
    public static String model = "staff"; // æ¨¡å—
    public static String setParent = "com.ruoyi."+ model; // åŒ…路径
    public static String tablePrefix = ""; // è®¾ç½®è¿‡æ»¤è¡¨å‰ç¼€
    public static void main(String[] args) {
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
@@ -12,6 +13,7 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -23,9 +25,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -103,9 +103,16 @@
            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            if (productModelList == null || productModelList.isEmpty()) {
            if (CollectionUtils.isEmpty(productModelList)) {
                return AjaxResult.error("导入数据不能为空");
            }
            //  èŽ·å–å½“å‰äº§å“ä¸‹æ‰€æœ‰çš„è§„æ ¼åž‹å·å
            List<ProductModel> existingModels = list(new LambdaQueryWrapper<ProductModel>().eq(ProductModel::getProductId, productId));
            Set<String> existingModelNames = existingModels.stream().map(ProductModel::getModel).collect(Collectors.toSet());
            List<ProductModel> waitToSaveList = new ArrayList<>();
            int skipCount = 0;
            for (int i = 0; i < productModelList.size(); i++) {
                ProductModel item = productModelList.get(i);
@@ -117,14 +124,31 @@
                if (StringUtils.isEmpty(item.getUnit())) {
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [单位] ä¸èƒ½ä¸ºç©º");
                }
                //  åŽ»é‡,如果已包含该型号,则跳过
                if (existingModelNames.contains(item.getModel())) {
                    skipCount++;
                    continue;
                }
                item.setProductId(product.getId());
                waitToSaveList.add(item);
                existingModelNames.add(item.getModel());
            }
            saveOrUpdateBatch(productModelList);
            return AjaxResult.success("成功导入 " + productModelList.size() + " æ¡æ•°æ®");
            if (!waitToSaveList.isEmpty()) {
                saveBatch(waitToSaveList);
            }
            if (skipCount == 0) {
                return AjaxResult.success(String.format("成功导入 %d æ¡æ•°æ®", waitToSaveList.size()));
            } else {
                return AjaxResult.success(String.format("成功导入 %d æ¡ï¼Œè·³è¿‡å·²å­˜åœ¨æ•°æ® %d æ¡", waitToSaveList.size(), skipCount));
            }
        } catch (Exception e) {
            log.error("导入产品规格异常", e);
            return AjaxResult.error("导入失败");
            throw new ServiceException("导入失败");
        }
    }
}
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java
@@ -5,17 +5,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -99,4 +93,11 @@
    {
        return toAjax(noticeService.readAll());
    }
    @PostMapping("appReadNotice")
    @ApiOperation("移动端根据消息ID进行已读")
    public AjaxResult appReadNotice(@RequestParam("noticeId") Long noticeId) {
        boolean result = noticeService.appReadNotice(noticeId);
        return toAjax(result);
    }
}
src/main/java/com/ruoyi/project/system/domain/SysNotice.java
@@ -3,8 +3,7 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -20,11 +19,13 @@
 * @author ruoyi
 */
@Data
@TableName("sys_notice")
public class SysNotice
{
    private static final long serialVersionUID = 1L;
    /** å…¬å‘ŠID */
    @TableId(value = "notice_id", type = IdType.AUTO)
    private Long noticeId;
    /** å…¬å‘Šæ ‡é¢˜ */
src/main/java/com/ruoyi/project/system/service/ISysNoticeService.java
@@ -82,4 +82,11 @@
     */
    void simpleNoticeAll(final String title, final String message,final String jumpPath);
    /**
     * APP点击推送消息更改为已读状态
     *
     * @param noticeId æ¶ˆæ¯ID
     * @return å¤±è´¥/成功
     */
    boolean appReadNotice(Long noticeId);
}
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -28,6 +28,7 @@
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.mapper.SysNoticeMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import org.springframework.transaction.annotation.Transactional;
/**
 * å…¬å‘Š æœåŠ¡å±‚å®žçŽ°
@@ -230,4 +231,20 @@
        return sysNotice;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean appReadNotice(Long noticeId) {
        if (noticeId == null) {
            return false;
        }
        SysNotice sysNotice = noticeMapper.selectNoticeById(noticeId);
        if (sysNotice == null) {
            return false;
        }
        sysNotice.setStatus("1");
        return noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
                .eq(SysNotice::getNoticeId, noticeId)
                .eq(SysNotice::getStatus, "0")
                .set(SysNotice::getStatus, "1")) > 0;
    }
}
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
@@ -1,5 +1,6 @@
package com.ruoyi.project.system.service.impl;
import com.alibaba.fastjson2.JSON;
import com.getui.push.v2.sdk.ApiHelper;
import com.getui.push.v2.sdk.GtApiConfiguration;
import com.getui.push.v2.sdk.api.PushApi;
@@ -25,6 +26,7 @@
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -84,6 +86,7 @@
            // æŽ¨é€
            sendRoutingPush(
                    sysNotice.getNoticeId(),
                    client.getCid(),
                    sysNotice.getNoticeTitle(),
                    sysNotice.getRemark() != null ? sysNotice.getRemark() : sysNotice.getNoticeContent(),
@@ -139,18 +142,23 @@
    /**
     * å‘送单人路由推送
     */
    private void sendRoutingPush(String cid, String title, String content, String targetPath) {
        log.info("准备推送消息: CID={}, Title={}, TargetPath={}", cid, title, targetPath);
    private void sendRoutingPush(Long noticeId, String cid, String title, String content, String targetPath) {
        log.info("准备推送消息:NoticeId={}, CID={}, Title={}, TargetPath={}", noticeId, cid, title, targetPath);
        PushDTO<Audience> pushDTO = new PushDTO<>();
        pushDTO.setRequestId("REQ_" + System.currentTimeMillis());
        // åœ¨çº¿é€ä¼ å†…容
        PushMessage pushMessage = new PushMessage();
        String transmissionContent = String.format(
                "{\"title\":\"%s\",\"content\":\"%s\",\"payload\":\"%s\"}",
                title, content, targetPath
        );
        Map<String, Object> pushMessageMap = new HashMap<>();
        Map<String, Object> payloadMap = new HashMap<>();
        pushMessageMap.put("title", title);
        pushMessageMap.put("content", content);
        payloadMap.put("url", targetPath);
        payloadMap.put("noticeId", noticeId);
        pushMessageMap.put("payload", JSON.toJSONString(payloadMap));
        String transmissionContent = JSON.toJSONString(pushMessageMap);
        pushMessage.setTransmission(transmissionContent);
        pushDTO.setPushMessage(pushMessage);
@@ -160,7 +168,7 @@
        pushDTO.setAudience(audience);
        // ç¦»çº¿æŽ¨é€é€šé“
        pushDTO.setPushChannel(getPushChannel(title, content, targetPath));
//        pushDTO.setPushChannel(getPushChannel(noticeId, title, content, targetPath));
        try {
            ApiResult<Map<String, Map<String, String>>> result = pushApi.pushToSingleByCid(pushDTO);
@@ -175,7 +183,7 @@
    }
    @NotNull
    private PushChannel getPushChannel(String title, String content, String targetPath) {
    private PushChannel getPushChannel(Long noticeId, String title, String content, String targetPath) {
        PushChannel pushChannel = new PushChannel();
        AndroidDTO androidDTO = new AndroidDTO();
        Ups ups = new Ups();
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -14,10 +14,7 @@
import com.ruoyi.sales.dto.InvoiceLedgerDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper;
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.pojo.InvoiceLedger;
import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
import com.ruoyi.sales.pojo.ReceiptPayment;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.service.ICommonFileService;
@@ -39,10 +36,7 @@
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -64,9 +58,6 @@
    @Autowired
    private InvoiceLedgerMapper invoiceLedgerMapper;
    @Autowired
    private InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
    @Autowired
    private ReceiptPaymentMapper receiptPaymentMapper;
@@ -267,69 +258,85 @@
     */
    @GetMapping("/listPage")
    public IPage<SalesLedger> listPage(Page page, SalesLedgerDto salesLedgerDto) {
        IPage<SalesLedger> iPage = salesLedgerService.selectSalesLedgerListPage(page,salesLedgerDto);
        // è®¡ç®—已开票金额/未开票金额(已填写发票金额为准)
        if(CollectionUtils.isEmpty(iPage.getRecords())){
            return iPage;
        }
        List<Long> salesLedgerIds = iPage.getRecords().stream().map(SalesLedger::getId).collect(Collectors.toList());
        List<InvoiceLedgerDto> invoiceLedgerDtoList = invoiceLedgerMapper.invoicedTotal(salesLedgerIds);
        if(CollectionUtils.isEmpty(invoiceLedgerDtoList)){
            return iPage;
        }
        // è®¡ç®—回款金额,待回款金额
        List<InvoiceRegistrationProduct> invoiceRegistrationProducts = invoiceRegistrationProductMapper.selectList(new LambdaQueryWrapper<InvoiceRegistrationProduct>()
                .in(InvoiceRegistrationProduct::getSalesLedgerId, salesLedgerIds));
        IPage<SalesLedger> iPage = salesLedgerService.selectSalesLedgerListPage(page, salesLedgerDto);
        List<InvoiceLedger> invoiceLedgers = invoiceLedgerMapper.selectList(new LambdaQueryWrapper<InvoiceLedger>()
                .in(InvoiceLedger::getInvoiceRegistrationProductId, invoiceRegistrationProducts.stream().map(InvoiceRegistrationProduct::getId).collect(Collectors.toList())));
        List<ReceiptPayment> receiptPayments = new ArrayList<>();
        if(!CollectionUtils.isEmpty(invoiceLedgers)){
            receiptPayments = receiptPaymentMapper.selectList(new LambdaQueryWrapper<ReceiptPayment>()
                    .in(ReceiptPayment::getInvoiceLedgerId, invoiceLedgers.stream().map(InvoiceLedger::getId).collect(Collectors.toList())));
        //  æŸ¥è¯¢ç»“果为空,直接返回
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            return iPage;
        }
        for (SalesLedger salesLedger : iPage.getRecords()) {
            boolean existFlag = false;
            BigDecimal noInvoiceAmountTotal = BigDecimal.ZERO;
            BigDecimal invoiceTotal = BigDecimal.ZERO;
            for (InvoiceLedgerDto invoiceLedgerDto : invoiceLedgerDtoList) {
                if (salesLedger.getId().intValue() == invoiceLedgerDto.getSalesLedgerId()) {
                    noInvoiceAmountTotal = salesLedger.getContractAmount().subtract(invoiceLedgerDto.getInvoiceTotal());
                    invoiceTotal = invoiceLedgerDto.getInvoiceTotal();
                    existFlag = true;
                    if(!CollectionUtils.isEmpty(receiptPayments)){
                        List<InvoiceRegistrationProduct> collect = invoiceRegistrationProducts.stream()
                                .filter(item -> salesLedger.getId().equals(Long.parseLong(item.getSalesLedgerId().toString())))
                                .collect(Collectors.toList());
                        List<Integer> collect1 = collect.stream()
                                .map(InvoiceRegistrationProduct::getId).collect(Collectors.toList());
                        List<InvoiceLedger> collect2 = invoiceLedgers.stream()
                                .filter(item -> collect1.contains(item.getInvoiceRegistrationProductId()))
                                .collect(Collectors.toList());
                        // èŽ·å–å·²å›žæ¬¾é‡‘é¢
                        List<ReceiptPayment> collect3 = receiptPayments.stream()
                                .filter(item -> collect2.stream().anyMatch(item1 -> item1.getId().equals(item.getInvoiceLedgerId())))
                                .collect(Collectors.toList());
                        BigDecimal receiptPaymentAmountTotal = collect3.stream().map(ReceiptPayment::getReceiptPaymentAmount)
                                .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
                        // èŽ·å–å¾…å›žæ¬¾é‡‘é¢
                        BigDecimal noReceiptPaymentAmountTotal = invoiceLedgerDto.getInvoiceTotal().subtract(receiptPaymentAmountTotal);
                        salesLedger.setReceiptPaymentAmountTotal(receiptPaymentAmountTotal);
                        salesLedger.setNoReceiptAmount(noReceiptPaymentAmountTotal);
                    }
                    break;
        //  èŽ·å–å½“å‰é¡µæ‰€æœ‰å°è´¦è®°å½•çš„ ID é›†åˆ
        List<Long> salesLedgerIds = iPage.getRecords().stream().map(SalesLedger::getId).collect(Collectors.toList());
        //  æŸ¥è¯¢å‘票信息的已开票金额
        List<InvoiceLedgerDto> invoiceLedgerDtoList = invoiceLedgerMapper.invoicedTotal(salesLedgerIds);
        if (CollectionUtils.isEmpty(invoiceLedgerDtoList)) {
            invoiceLedgerDtoList = Collections.emptyList();
        }
        //  è½¬æ¢å‘票数据, key ä¸ºå°è´¦ID, value ä¸ºè¯¥å°è´¦çš„æ€»å¼€ç¥¨é‡‘额
        Map<Long, BigDecimal> invoiceTotals = invoiceLedgerDtoList.stream()
                .filter(dto -> dto.getSalesLedgerId() != null && dto.getInvoiceTotal() != null)
                .collect(Collectors.toMap(
                        dto -> dto.getSalesLedgerId().longValue(),
                        InvoiceLedgerDto::getInvoiceTotal,
                        BigDecimal::add // å­˜åœ¨é‡å¤ID执行累加
                ));
        //  æŸ¥è¯¢å›žæ¬¾/付款记录
        List<ReceiptPayment> receiptPayments = Collections.emptyList();
        if (!CollectionUtils.isEmpty(salesLedgerIds)) {
            receiptPayments = receiptPaymentMapper.selectList(new LambdaQueryWrapper<ReceiptPayment>()
                    .in(ReceiptPayment::getSalesLedgerId, salesLedgerIds));
        }
        //  è½¬æ¢å›žæ¬¾æ•°æ®, key ä¸ºå°è´¦ID, value ä¸ºè¯¥å°è´¦çš„æ€»å›žæ¬¾é‡‘额
        Map<Long, BigDecimal> receiptTotals = new HashMap<>();
        if (!CollectionUtils.isEmpty(receiptPayments)) {
            for (ReceiptPayment receiptPayment : receiptPayments) {
                if (receiptPayment.getSalesLedgerId() != null && receiptPayment.getReceiptPaymentAmount() != null) {
                    //  å¦‚æžœ key å­˜åœ¨åˆ™ç›¸åŠ ,不存在则放入
                    receiptTotals.merge(receiptPayment.getSalesLedgerId(), receiptPayment.getReceiptPaymentAmount(), BigDecimal::add);
                }
            }
            if(existFlag){
                salesLedger.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            }else {
                salesLedger.setNoInvoiceAmountTotal(salesLedger.getContractAmount());
            }
            salesLedger.setInvoiceTotal(invoiceTotal);
        }
        for (SalesLedger salesLedger : iPage.getRecords()) {
            Long ledgerId = salesLedger.getId();
            // åˆåŒæ€»é‡‘额
            BigDecimal contractAmount = salesLedger.getContractAmount() == null ? BigDecimal.ZERO : salesLedger.getContractAmount();
            // å¼€ç¥¨æ€»é¢å’Œå›žæ¬¾æ€»é¢
            BigDecimal invoiceTotal = invoiceTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            BigDecimal receiptPaymentAmountTotal = receiptTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            //  æœªå¼€ç¥¨é‡‘额 = åˆåŒé‡‘额 - å·²å¼€ç¥¨é‡‘额
            BigDecimal noInvoiceAmountTotal = contractAmount.subtract(invoiceTotal);
            if (noInvoiceAmountTotal.compareTo(BigDecimal.ZERO) < 0) {
                noInvoiceAmountTotal = BigDecimal.ZERO;
            }
            //  å¾…回款金额 = å·²å¼€ç¥¨é‡‘额 - å·²å›žæ¬¾é‡‘额
            BigDecimal noReceiptPaymentAmountTotal = invoiceTotal.subtract(receiptPaymentAmountTotal);
            if (noReceiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) < 0) {
                noReceiptPaymentAmountTotal = BigDecimal.ZERO;
            }
            salesLedger.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            salesLedger.setInvoiceTotal(invoiceTotal);
            salesLedger.setReceiptPaymentAmountTotal(receiptPaymentAmountTotal);
            salesLedger.setNoReceiptAmount(noReceiptPaymentAmountTotal);
            //  å¦‚果已经有过开票或回款操作,则不允许编辑
            boolean hasInvoiceOperation = invoiceTotal.compareTo(BigDecimal.ZERO) > 0;
            boolean hasReceiptOperation = receiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) > 0;
            salesLedger.setIsEdit(!(hasInvoiceOperation || hasReceiptOperation));
        }
        if (ObjectUtils.isNotEmpty(salesLedgerDto.getStatus())) {
            if (salesLedgerDto.getStatus()) {
                iPage.getRecords().removeIf(salesLedger -> Objects.equals(salesLedger.getNoInvoiceAmountTotal(), new BigDecimal("0.00")));
                // æ¸…除所有“未开票金额”为 0 çš„记录
                iPage.getRecords().removeIf(salesLedger ->
                        Objects.equals(salesLedger.getNoInvoiceAmountTotal(), new BigDecimal("0.00")));
                iPage.setTotal(iPage.getRecords().size());
            }
        }
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -72,15 +72,14 @@
     * æŸ¥è¯¢äº§å“ä¿¡æ¯åˆ—表
     */
    @GetMapping("/list")
    public AjaxResult list(SalesLedgerProduct salesLedgerProduct)
    {
    public AjaxResult list(SalesLedgerProduct salesLedgerProduct) {
        List<SalesLedgerProduct> list = salesLedgerProductService.selectSalesLedgerProductList(salesLedgerProduct);
        list.forEach(item -> {
                if (item.getFutureTickets().compareTo(BigDecimal.ZERO) == 0) {
                    item.setFutureTickets(item.getQuantity());
                }
            if (item.getFutureTickets().compareTo(BigDecimal.ZERO) == 0) {
                item.setFutureTickets(BigDecimal.ZERO);
            }
            if (item.getFutureTicketsAmount().compareTo(BigDecimal.ZERO) == 0) {
                item.setFutureTicketsAmount(item.getTaxInclusiveTotalPrice());
                item.setFutureTicketsAmount(BigDecimal.ZERO);
            }
//            ProcurementPageDto procurementDto = new ProcurementPageDto();
//            procurementDto.setSalesLedgerProductId(item.getId());
@@ -89,13 +88,13 @@
//            BigDecimal stockQuantity = stockUtils.getStockQuantity(item.getProductModelId()).get("stockQuantity");
//                ProcurementPageDtoCopy procurementDtoCopy = result.getRecords().get(0);
                if (item.getApproveStatus() != 2) {
                    if (item.getHasSufficientStock() == 0) {
                        item.setApproveStatus(0);
                    }else {
                        item.setApproveStatus(1);
                    }
            if (item.getApproveStatus() != 2) {
                if (item.getHasSufficientStock() == 0) {
                    item.setApproveStatus(0);
                } else {
                    item.setApproveStatus(1);
                }
            }
        });
        return AjaxResult.success(list);
    }
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -143,5 +143,9 @@
    @TableField(exist = false)
    //是否发货(台账页面颜色控制)
    private Boolean isFh;
    @TableField(exist = false)
    //是否可编辑
    private Boolean isEdit;
}
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java
@@ -3,7 +3,6 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -13,7 +12,10 @@
import com.ruoyi.sales.dto.InvoiceRegistrationProductDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.excel.InvoiceRegisAndProductExcelDto;
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.InvoiceLedger;
import com.ruoyi.sales.pojo.InvoiceRegistration;
import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
@@ -85,18 +87,16 @@
                throw new RuntimeException("销售台账产品不存在,ID:" + productDatum.getId());
            }
            //  è®¡ç®—累计开票
            BigDecimal newInvoiceNum = dbProduct.getInvoiceNum().add(currentInvoiceNum);
            // æœªå¼€ç¥¨
            BigDecimal noInvoiceAmount = dbProduct.getNoInvoiceAmount();
            BigDecimal noInvoiceNum = dbProduct.getNoInvoiceNum();
            BigDecimal newInvoiceAmount = dbProduct.getInvoiceAmount().add(currentInvoiceAmount);
            // å‰©ä½™æœªå¼€ç¥¨
            BigDecimal newNoInvoiceNum = noInvoiceNum.subtract(currentInvoiceNum);
            BigDecimal newNoInvoiceAmount = noInvoiceAmount.subtract(currentInvoiceAmount);
            //  è®¡ç®—未开票
            BigDecimal newNoInvoiceNum = dbProduct.getQuantity().subtract(newInvoiceNum);
            BigDecimal newNoInvoiceAmount = dbProduct.getTaxInclusiveTotalPrice().subtract(newInvoiceAmount);
            if (newNoInvoiceNum.compareTo(BigDecimal.ZERO) < 0
                    || newNoInvoiceAmount.compareTo(BigDecimal.ZERO) < 0) {
            if (newNoInvoiceAmount.compareTo(BigDecimal.ZERO) < 0) {
                throw new RuntimeException("开票数量或金额超过合同总量");
            }
@@ -105,8 +105,6 @@
                    null,
                    new LambdaUpdateWrapper<SalesLedgerProduct>()
                            .eq(SalesLedgerProduct::getId, dbProduct.getId())
                            .set(SalesLedgerProduct::getInvoiceNum, newInvoiceNum)
                            .set(SalesLedgerProduct::getInvoiceAmount, newInvoiceAmount)
                            .set(SalesLedgerProduct::getNoInvoiceNum, newNoInvoiceNum)
                            .set(SalesLedgerProduct::getNoInvoiceAmount, newNoInvoiceAmount)
            );
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -72,6 +72,7 @@
        byId.setExpressCompany(req.getExpressCompany());
        byId.setStatus("已发货");
        byId.setShippingCarNumber(req.getShippingCarNumber());
        byId.setShippingDate(req.getShippingDate());
        boolean update = this.updateById(byId);
        // è¿ç§»æ–‡ä»¶
        if(CollectionUtils.isNotEmpty(req.getTempFileIds())){
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.staff.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@RestController
@RequestMapping("/personalAttendanceLocationConfig")
@Api(tags = "人员打卡规则配置")
public class PersonalAttendanceLocationConfigController {
    @Autowired
    private PersonalAttendanceLocationConfigService personalAttendanceLocationConfigService;
    @ApiOperation("新增/修改人员打卡规则配置")
    @PostMapping("/add")
    public R add(@RequestBody PersonalAttendanceLocationConfig personalAttendanceLocationConfig){
        return R.ok(personalAttendanceLocationConfigService.saveOrUpdate(personalAttendanceLocationConfig));
    }
    @ApiOperation("分页查询人员打卡规则配置")
    @GetMapping("/listPage")
    public R listPage(Page page){
        return R.ok(personalAttendanceLocationConfigService.page(page));
    }
    @ApiOperation("删除人员打卡规则配置")
    @DeleteMapping("/del")
    public R del(@RequestBody List<Integer> ids) {
        return R.ok(personalAttendanceLocationConfigService.removeBatchByIds(ids));
    }
}
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java
@@ -6,6 +6,7 @@
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -21,30 +22,30 @@
 */
@RestController
@RequestMapping("/personalAttendanceRecords")
@Api(tags = "打卡签到")
@Api(tags = "人员打卡签到")
public class PersonalAttendanceRecordsController {
    @Resource
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    // æ–°å¢ž
    @ApiOperation("新增打卡签到")
    @PostMapping("")
    public AjaxResult add(@RequestBody PersonalAttendanceRecords personalAttendanceRecord){
        return AjaxResult.success(personalAttendanceRecordsService.add(personalAttendanceRecord));
    public AjaxResult add(@RequestBody PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.add(personalAttendanceRecordsDto));
    }
    // åˆ—表查询
    @ApiOperation("分页查询打卡签到")
    @GetMapping("/listPage")
    public AjaxResult listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.listPage(page, personalAttendanceRecordsDto));
    }
    // ä»Šæ—¥è€ƒå‹¤æ•°æ®
    @ApiOperation("获取当前人的考勤相关数据")
    @GetMapping("/today")
    public AjaxResult todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.todayInfo(personalAttendanceRecordsDto));
    }
    // å¯¼å‡º
    @ApiOperation("导出打卡签到")
    @PostMapping("/export")
    public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        personalAttendanceRecordsService.export(response, personalAttendanceRecordsDto);
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java
@@ -1,10 +1,18 @@
package com.ruoyi.staff.dto;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Data
@ExcelIgnoreUnannotated
public class PersonalAttendanceRecordsDto extends PersonalAttendanceRecords {
    @Excel(name = "姓名", sort = 3)
    private String staffName;
@@ -16,4 +24,20 @@
    private String deptName;
    private Long deptId;
    //打卡的经度
    private Double longitude;
    //打卡的纬度
    private Double latitude;
    //标准上班时间
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime startAt;
    //标准下班时间
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
}
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceLocationConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.staff.mapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Mapper
public interface PersonalAttendanceLocationConfigMapper extends BaseMapper<PersonalAttendanceLocationConfig> {
}
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.staff.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.LocalTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Getter
@Setter
@TableName("personal_attendance_location_config")
@ApiModel(value = "PersonalAttendanceLocationConfig对象", description = "人员打卡规则配置")
public class PersonalAttendanceLocationConfig implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("部门id")
    private Integer sysDeptId;
    @ApiModelProperty("地点名称")
    private String locationName;
    @ApiModelProperty("经度")
    private Double longitude;
    @ApiModelProperty("纬度")
    private Double latitude;
    @ApiModelProperty("打卡范围")
    private Double radius;
    @ApiModelProperty("上班时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime startAt;
    @ApiModelProperty("下班时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
}
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.staff.service;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
public interface PersonalAttendanceLocationConfigService extends IService<PersonalAttendanceLocationConfig> {
}
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java
@@ -21,7 +21,7 @@
public interface PersonalAttendanceRecordsService extends IService<PersonalAttendanceRecords> {
    IPage<PersonalAttendanceRecordsDto> listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    int add(PersonalAttendanceRecords personalAttendanceRecords);
    int add(PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceLocationConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Service
public class PersonalAttendanceLocationConfigServiceImpl extends ServiceImpl<PersonalAttendanceLocationConfigMapper, PersonalAttendanceLocationConfig> implements PersonalAttendanceLocationConfigService {
}
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
@@ -12,13 +13,16 @@
import com.ruoyi.project.system.service.ISysDictDataService;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.staff.task.PersonalAttendanceRecordsTask;
import com.ruoyi.staff.utils.LocationUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -29,6 +33,7 @@
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@@ -41,6 +46,7 @@
 * @since 2026-02-09 01:20:07
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class PersonalAttendanceRecordsServiceImpl extends ServiceImpl<PersonalAttendanceRecordsMapper, PersonalAttendanceRecords> implements PersonalAttendanceRecordsService {
    @Autowired
    private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper;
@@ -49,7 +55,7 @@
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private PersonalAttendanceRecordsTask personalAttendanceRecordsTask;
    private PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
    @Autowired
    private ISysDictDataService dictDataService;
@@ -58,37 +64,52 @@
    private SysDeptMapper sysDeptMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int add(PersonalAttendanceRecords personalAttendanceRecords) {
    public int add(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        // å½“前时间
        LocalDate currentDate = LocalDate.now();
        // é¦–先根据用户ID查询员工信息
        LocalDateTime currentDateTime = LocalDateTime.now();
        /*查询员工信息*/
        QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
        staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
        StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
        if (staffOnJob == null) {
            throw new BaseException("当前用户没有对应的员工信息");
        }
        // å½“前时间
        LocalDateTime currentDateTime = LocalDateTime.now();
        // å¦‚果打卡时间超过考勤下班时间不能打卡
        /*判断打卡位置是否在规则范围内*/
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs == null || personalAttendanceLocationConfigs.isEmpty()) {
            throw new BaseException("当前部门没有设置打卡规则");
        }
        Double punchLongitude = personalAttendanceRecordsDto.getLongitude(); //打卡的经度
        Double punchLatitude = personalAttendanceRecordsDto.getLatitude(); // æ‰“卡的纬度
        if (punchLongitude == null || punchLatitude == null) {
            throw new BaseException("打卡失败:未获取到您的位置信息,请开启定位权限");
        }
        //计算打卡位置与考勤点的距离
        PersonalAttendanceLocationConfig locationConfig = personalAttendanceLocationConfigs.get(0);//获取最新的一条数据
        double allowedRadius = locationConfig.getRadius(); // å…è®¸çš„范围(米)
        double actualDistance = LocationUtils.calculateDistance(
                punchLatitude, punchLongitude, // å‘˜å·¥æ‰“卡的经纬度
                locationConfig.getLatitude(), locationConfig.getLongitude() // è€ƒå‹¤ç‚¹çš„经纬度
        );
        //判断是否在范围内
        if (actualDistance > allowedRadius) {
            throw new BaseException(String.format("打卡失败:您当前位置距离考勤点%.2f米,超出允许范围(%s米)", actualDistance, allowedRadius));
        }
        /*判断打卡时间*/
        LocalTime   endAt = locationConfig.getEndAt(); //下班时间
        // èŽ·å–è€ƒå‹¤ä¸‹ç­æ—¶é—´ç‚¹
        String[] timeConfigs = getAttendanceTimeConfig();
        String timeConfig = timeConfigs[1];
        String[] timeParts = timeConfig.split(":");
        int standardHour = Integer.parseInt(timeParts[0]);
        int standardMinute = Integer.parseInt(timeParts[1]);
        int standardHour = endAt.getHour();
        int standardMinute = endAt.getMinute();
        // å½“前时间
        int actualHour = currentDateTime.getHour();
        int actualMinute = currentDateTime.getMinute();
        // åˆ¤æ–­æ‰“卡时间是否晚于当前时间
        if (actualHour > standardHour || (actualHour == standardHour && actualMinute > standardMinute)) {
            throw new BaseException("打卡时间不能晚于下班时间");
            throw new BaseException(String.format("打卡失败:打卡时间不能晚于下班时间(%02d:%02d)", standardHour, standardMinute));
        }
        // æ ¹æ®å‘˜å·¥ID和当前日期查询打卡记录
        QueryWrapper<PersonalAttendanceRecords> attendanceQueryWrapper = new QueryWrapper<>();
        attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId())
@@ -97,10 +118,11 @@
        // æ ¹æ®å­—典设置的考勤时间判断迟到早退
        if (attendanceRecord == null) {
            // ä¸å­˜åœ¨æ‰“卡记录,创建新记录
            PersonalAttendanceRecords personalAttendanceRecords = new PersonalAttendanceRecords();
            personalAttendanceRecords.setStaffOnJobId(staffOnJob.getId());
            personalAttendanceRecords.setDate(currentDate);
            personalAttendanceRecords.setWorkStartAt(currentDateTime);
            personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords, true));
            personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords, true,locationConfig));
            personalAttendanceRecords.setRemark(personalAttendanceRecords.getRemark());
            personalAttendanceRecords.setTenantId(staffOnJob.getTenantId());
            return personalAttendanceRecordsMapper.insert(personalAttendanceRecords);
@@ -117,7 +139,7 @@
                        .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
                attendanceRecord.setWorkHours(workHours);
                // æ›´æ–°è€ƒå‹¤çŠ¶æ€
                attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord, false));
                attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord, false,locationConfig));
                return personalAttendanceRecordsMapper.updateById(attendanceRecord);
            } else {
                throw new BaseException("您已经打过卡了,无需重复打卡!!!");
@@ -125,46 +147,22 @@
        }
    }
    // èŽ·å–è€ƒå‹¤æ—¶é—´é…ç½®
    private String[] getAttendanceTimeConfig() {
        String[] timeConfigs = new String[2];
        try {
            String dictType = "sys_work_time";
            // èŽ·å–ä¸Šç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º09:00
            String startTimeConfig = dictDataService.selectDictLabel(dictType, "start_at");
            timeConfigs[0] = (startTimeConfig == null || startTimeConfig.trim().isEmpty()) ? "09:00" : startTimeConfig;
            // èŽ·å–ä¸‹ç­æ—¶é—´é…ç½®ï¼Œé»˜è®¤ä¸º18:00
            String endTimeConfig = dictDataService.selectDictLabel(dictType, "end_at");
            timeConfigs[1] = (endTimeConfig == null || endTimeConfig.trim().isEmpty()) ? "18:00" : endTimeConfig;
            return timeConfigs;
        } catch (Exception e) {
            timeConfigs[0] = "09:00"; // é»˜è®¤ä¸Šç­æ—¶é—´
            timeConfigs[1] = "18:00"; // é»˜è®¤ä¸‹ç­æ—¶é—´
            return timeConfigs;
        }
    }
    // æ ¹æ®å®žé™…时间和是否上班时间判断考勤状态
    // 0 æ­£å¸¸ 1 è¿Ÿåˆ° 2 æ—©é€€ 3 è¿Ÿåˆ°æ—©é€€ 4 ç¼ºå‹¤
    private Integer determineAttendanceStatus(PersonalAttendanceRecords attendanceRecord, boolean isStart) {
    private Integer determineAttendanceStatus(PersonalAttendanceRecords attendanceRecord, boolean isStart,PersonalAttendanceLocationConfig locationConfig) {
        //判断是上班打卡还是下班打卡
        LocalDateTime actualTime = isStart ? attendanceRecord.getWorkStartAt() : attendanceRecord.getWorkEndAt();
        try {
            // èŽ·å–è€ƒå‹¤æ—¶é—´é…ç½®
            String[] timeConfigs = getAttendanceTimeConfig();
            String timeConfig = isStart ? timeConfigs[0] : timeConfigs[1];
            // è§£æžæ ‡å‡†æ—¶é—´
            String[] timeParts = timeConfig.split(":");
            int standardHour = Integer.parseInt(timeParts[0]);
            int standardMinute = Integer.parseInt(timeParts[1]);
            LocalTime startAt = locationConfig.getStartAt();//上班时间
            LocalTime  endAt = locationConfig.getEndAt();//下班时间
            LocalTime  timeConfig = isStart ? startAt : endAt;
            // è§£æžå°æ—¶å’Œåˆ†é’Ÿ
            int standardHour = timeConfig.getHour();
            int standardMinute = timeConfig.getMinute();
            // èŽ·å–å®žé™…æ—¶é—´çš„æ—¶åˆ†
            int actualHour = actualTime.getHour();
            int actualMinute = actualTime.getMinute();
            // åˆ¤æ–­çŠ¶æ€
            if (isStart) {
                // ä¸Šç­æ‰“卡:超过标准时间算迟到
@@ -180,9 +178,7 @@
                    return 2; // æ—©é€€
                }
            }
            return 0; // æ­£å¸¸
        } catch (Exception e) {
            // å¦‚果获取配置失败,默认返回正常状态
            log.warn("获取考勤时间配置失败,使用默认状态:" + e.getMessage());
@@ -240,7 +236,14 @@
        resultDto.setDeptId(staffOnJob.getSysDeptId() != null ? staffOnJob.getSysDeptId() : null);
        SysDept dept = sysDeptMapper.selectDeptById(staffOnJob.getSysDeptId());
        resultDto.setDeptName(dept != null ? dept.getDeptName() : null);
        //获取该员工对应的打卡规则
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs.size()>0){
            resultDto.setStartAt(personalAttendanceLocationConfigs.get(0).getStartAt());
            resultDto.setEndAt(personalAttendanceLocationConfigs.get(0).getEndAt());
        }
        return resultDto;
    }
src/main/java/com/ruoyi/staff/utils/LocationUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.staff.utils;
// å·¥å…·ç±»ï¼šè®¡ç®—两个经纬度之间的距离(球面距离)
public class LocationUtils {
    private static final double EARTH_RADIUS = 6371000; // åœ°çƒåŠå¾„,单位米
    /**
     * è®¡ç®—两个经纬度之间的距离(米)
     * @param lat1 ç¬¬ä¸€ä¸ªç‚¹çº¬åº¦
     * @param lon1 ç¬¬ä¸€ä¸ªç‚¹ç»åº¦
     * @param lat2 ç¬¬äºŒä¸ªç‚¹çº¬åº¦
     * @param lon2 ç¬¬äºŒä¸ªç‚¹ç»åº¦
     * @return è·ç¦»ï¼ˆç±³ï¼‰
     */
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        // è½¬å¼§åº¦
        double radLat1 = Math.toRadians(lat1);
        double radLon1 = Math.toRadians(lon1);
        double radLat2 = Math.toRadians(lat2);
        double radLon2 = Math.toRadians(lon2);
        // å·®å€¼
        double deltaLat = radLat1 - radLat2;
        double deltaLon = radLon1 - radLon2;
        // çƒé¢è·ç¦»å…¬å¼
        double distance = 2 * Math.asin(Math.sqrt(
                Math.pow(Math.sin(deltaLat / 2), 2) +
                        Math.cos(radLat1) * Math.cos(radLat2) *
                                Math.pow(Math.sin(deltaLon / 2), 2)
        ));
        distance = distance * EARTH_RADIUS;
        // ä¿ç•™ä¸¤ä½å°æ•°
        distance = Math.round(distance * 100) / 100.0;
        return distance;
    }
}
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -9,17 +9,18 @@
        SET contract_amount = #{totalTaxInclusiveAmount}
        WHERE id = #{id}
    </update>
    <select id="selectPurchaseLedgerListPage" resultType="com.ruoyi.purchase.dto.PurchaseLedgerDto">
        select
        SELECT
        pl.id,
        pl.purchase_contract_number ,
        pl.purchase_contract_number,
        pl.sales_contract_no,
        pl.supplier_id,
        pl.supplier_name,
        pl.project_name,
        pl.contract_amount,
        IFNULL(sum(tr.invoice_amount),0) as receipt_payment_amount,
        pl.contract_amount-IFNULL(sum(tr.invoice_amount),0) AS unReceipt_payment_amount,
        IFNULL(tr_sum.total_invoice_amount, 0) AS receipt_payment_amount,
        pl.contract_amount - IFNULL(tr_sum.total_invoice_amount, 0) AS unReceipt_payment_amount,
        pl.entry_date,
        pl.execution_date,
        pl.recorder_id,
@@ -30,42 +31,41 @@
        pl.approval_status,
        pl.payment_method,
        pl.remarks
        from purchase_ledger pl
        left join sales_ledger_product slp on slp.sales_ledger_id = pl.id and slp.type=2
        left join product_record pr on pl.id = pr.purchase_ledger_id
        left join ticket_registration tr on tr.id = pr.ticket_registration_id
        left join supplier_manage sm on pl.supplier_id = sm.id
        FROM purchase_ledger pl
        LEFT JOIN (
        SELECT
        purchase_ledger_id,
        SUM(invoice_amount) AS total_invoice_amount
        FROM ticket_registration
        GROUP BY purchase_ledger_id
        ) tr_sum ON pl.id = tr_sum.purchase_ledger_id
        LEFT JOIN supplier_manage sm ON pl.supplier_id = sm.id
        <where>
            1 = 1
            <if test="c.purchaseContractNumber != null and c.purchaseContractNumber != ''">
               and pl.purchase_contract_number like concat('%',#{c.purchaseContractNumber},'%')
                AND pl.purchase_contract_number LIKE CONCAT('%', #{c.purchaseContractNumber}, '%')
            </if>
            <if test="c.approvalStatus != null and c.approvalStatus != ''">
                and pl.approval_status = #{c.approvalStatus}
                AND pl.approval_status = #{c.approvalStatus}
            </if>
            <if test="c.supplierName != null and c.supplierName != ''">
                and pl.supplier_name like concat('%',#{c.supplierName},'%')
                AND pl.supplier_name LIKE CONCAT('%', #{c.supplierName}, '%')
            </if>
            <if test="c.salesContractNo != null and c.salesContractNo != ''">
                and pl.sales_contract_no like concat('%',#{c.salesContractNo},'%')
                AND pl.sales_contract_no LIKE CONCAT('%', #{c.salesContractNo}, '%')
            </if>
            <if test="c.projectName != null and c.projectName != ''">
                and pl.project_name like concat('%',#{c.projectName},'%')
                AND pl.project_name LIKE CONCAT('%', #{c.projectName}, '%')
            </if>
            <if test="c.entryDateStart != null and c.entryDateStart != '' ">
                AND pl.entry_date &gt;= DATE_FORMAT(#{c.entryDateStart},'%Y-%m-%d')
            <if test="c.entryDateStart != null and c.entryDateStart != ''">
                AND pl.entry_date &gt;= #{c.entryDateStart}
            </if>
            <if test="c.entryDateEnd != null and c.entryDateEnd != '' ">
                AND  pl.entry_date &lt;= DATE_FORMAT(#{c.entryDateEnd},'%Y-%m-%d')
            <if test="c.entryDateEnd != null and c.entryDateEnd != ''">
                AND pl.entry_date &lt;= #{c.entryDateEnd}
            </if>
        </where>
        group by pl.id, pl.purchase_contract_number, pl.sales_contract_no, pl.supplier_name,
        pl.project_name,pl.entry_date,
        pl.recorder_name,
        pl.contract_amount
        order by pl.entry_date desc
        ORDER BY pl.entry_date DESC
    </select>
    <select id="getPaymentRegistrationDtoById" resultType="com.ruoyi.purchase.dto.PaymentRegistrationDto">
        SELECT
            T1.id,
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -8,23 +8,23 @@
        SELECT
        T1.*,
        CASE
        WHEN t2.qualitity-t2.locked_quantity >= T1.quantity THEN 1
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >= IFNULL(T1.quantity, 0) THEN 1
        ELSE 0
        END as has_sufficient_stock
        FROM
        sales_ledger_product T1
        LEFT JOIN stock_inventory t2 ON T1.product_model_id = t2.product_model_id
        <where>
            1=1
            <if test="salesLedgerProduct.salesLedgerId != null and salesLedgerProduct.salesLedgerId != '' ">
            <if test="salesLedgerProduct.salesLedgerId != null">
                AND T1.sales_ledger_id = #{salesLedgerProduct.salesLedgerId}
            </if>
            <if test="salesLedgerProduct.type != null and salesLedgerProduct.type != '' ">
            <if test="salesLedgerProduct.type != null">
                AND T1.type = #{salesLedgerProduct.type}
            </if>
        </where>
        ORDER BY T1.register_date DESC
    </select>
    <select id="selectSalesLedgerProductByMainId" resultType="com.ruoyi.sales.pojo.SalesLedgerProduct">
        select slp.*
        from quality_inspect qi
src/main/resources/mapper/staff/PersonalAttendanceLocationConfigMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<?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.staff.mapper.PersonalAttendanceLocationConfigMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig">
        <id column="id" property="id" />
        <result column="sys_dept_id" property="sysDeptId" />
        <result column="location_name" property="locationName" />
        <result column="longitude" property="longitude" />
        <result column="latitude" property="latitude" />
        <result column="radius" property="radius" />
        <result column="start_at" property="startAt" />
        <result column="end_at" property="endAt" />
    </resultMap>
</mapper>