Controller层
PillDrugControlle
package com.ruoyi.web.controller.pill;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.pill.domain.PillDrug;
import com.ruoyi.pill.service.IPillDrugService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 药品信息Controller
 *
 * @author wuweiqiang
 * @date 2025-05-19
 */
@RestController
@RequestMapping("/pill/drug")
public class PillDrugController extends BaseController
{
    @Autowired
    private IPillDrugService pillDrugService;

    /**
     * 查询药品信息列表
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:list')")
    @GetMapping("/list")
    public TableDataInfo list(PillDrug pillDrug)
    {
        startPage();
        List<PillDrug> list = pillDrugService.selectPillDrugList(pillDrug);
        return getDataTable(list);
    }

    /**
     * 导出药品信息列表
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:export')")
    @Log(title = "药品信息", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PillDrug pillDrug)
    {
        List<PillDrug> list = pillDrugService.selectPillDrugList(pillDrug);
        ExcelUtil<PillDrug> util = new ExcelUtil<PillDrug>(PillDrug.class);
        util.exportExcel(response, list, "药品信息数据");
    }

    /**
     * 获取药品信息详细信息
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:query')")
    @GetMapping(value = "/{drugId}")
    public AjaxResult getInfo(@PathVariable("drugId") Long drugId)
    {
        return success(pillDrugService.selectPillDrugByDrugId(drugId));
    }

    /**
     * 新增药品信息
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:add')")
    @Log(title = "药品信息", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody PillDrug pillDrug)
    {
        return toAjax(pillDrugService.insertPillDrug(pillDrug));
    }

    /**
     * 修改药品信息
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:edit')")
    @Log(title = "药品信息", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody PillDrug pillDrug)
    {
        return toAjax(pillDrugService.updatePillDrug(pillDrug));
    }

    /**
     * 删除药品信息
     */
    @PreAuthorize("@ss.hasPermi('pill:drug:remove')")
    @Log(title = "药品信息", businessType = BusinessType.DELETE)
   @DeleteMapping("/{drugIds}")
    public AjaxResult remove(@PathVariable Long[] drugIds)
    {
        return toAjax(pillDrugService.deletePillDrugByDrugIds(drugIds));
    }
}

2.
package com.ruoyi.web.controller.pill;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.pill.domain.PillExpiryWarning;
import com.ruoyi.pill.service.IPillExpiryWarningService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 药品有效期预警，用于监控药品保质期Controller
 *
 * @author wuweiqiang
 * @date 2025-05-20
 */
@RestController
@RequestMapping("/pill/warning")
public class PillExpiryWarningController extends BaseController
{


    
    @Autowired
    private IPillExpiryWarningService pillExpiryWarningService;

    @PreAuthorize("@ss.hasPermi('pill:warning:list')")
    @GetMapping("/list")
    public TableDataInfo list(PillExpiryWarning pillExpiryWarning)
    {
        startPage();
        // 确保onlyExpire会映射到实体，并传给service和mapper
        List<PillExpiryWarning> list = pillExpiryWarningService.selectPillExpiryWarningList(pillExpiryWarning);
        return getDataTable(list);
    }

    @PreAuthorize("@ss.hasPermi('pill:warning:export')")
    @Log(title = "药品有效期预警，用于监控药品保质期", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PillExpiryWarning pillExpiryWarning)
    {
        List<PillExpiryWarning> list = pillExpiryWarningService.selectPillExpiryWarningList(pillExpiryWarning);
        ExcelUtil<PillExpiryWarning> util = new ExcelUtil<PillExpiryWarning>(PillExpiryWarning.class);
        util.exportExcel(response, list, "药品有效期预警，用于监控药品保质期数据");
    }

    /**
     * 获取药品有效期预警，用于监控药品保质期详细信息
     */
    @PreAuthorize("@ss.hasPermi('pill:warning:query')")
    @GetMapping(value = "/{warningId}")
    public AjaxResult getInfo(@PathVariable("warningId") Long warningId)
    {
        return success(pillExpiryWarningService.selectPillExpiryWarningByWarningId(warningId));
    }

    /**
     * 新增药品有效期预警，用于监控药品保质期
     */
    @PreAuthorize("@ss.hasPermi('pill:warning:add')")
    @Log(title = "药品有效期预警，用于监控药品保质期", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody PillExpiryWarning pillExpiryWarning)
    {
        return toAjax(pillExpiryWarningService.insertPillExpiryWarning(pillExpiryWarning));
    }

    /**
     * 修改药品有效期预警，用于监控药品保质期
     */
    @PreAuthorize("@ss.hasPermi('pill:warning:edit')")
    @Log(title = "药品有效期预警，用于监控药品保质期", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody PillExpiryWarning pillExpiryWarning)
    {
        return toAjax(pillExpiryWarningService.updatePillExpiryWarning(pillExpiryWarning));
    }

    /**
     * 删除药品有效期预警，用于监控药品保质期
     */
    @PreAuthorize("@ss.hasPermi('pill:warning:remove')")
    @Log(title = "药品有效期预警，用于监控药品保质期", businessType = BusinessType.DELETE)
   @DeleteMapping("/{warningIds}")
    public AjaxResult remove(@PathVariable Long[] warningIds)
    {
        return toAjax(pillExpiryWarningService.deletePillExpiryWarningByWarningIds(warningIds));
    }
}

3.
package com.ruoyi.web.controller.pill;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.pill.domain.PillFactory;
import com.ruoyi.pill.service.IPillFactoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 生产厂家信息Controller
 *
 * @author wuweiqiang
 * @date 2025-05-18
 */
@RestController
@RequestMapping("/pill/factory")
public class PillFactoryController extends BaseController
{
    @Autowired
    private IPillFactoryService pillFactoryService;

    /**
     * 查询生产厂家信息列表
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:list')")
    @GetMapping("/list")
    public TableDataInfo list(PillFactory pillFactory)
    {
        startPage();
        List<PillFactory> list = pillFactoryService.selectPillFactoryList(pillFactory);
        return getDataTable(list);
    }

    /**
     * 导出生产厂家信息列表
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:export')")
    @Log(title = "生产厂家信息", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PillFactory pillFactory)
    {
        List<PillFactory> list = pillFactoryService.selectPillFactoryList(pillFactory);
        ExcelUtil<PillFactory> util = new ExcelUtil<PillFactory>(PillFactory.class);
        util.exportExcel(response, list, "生产厂家信息数据");
    }

    /**
     * 获取生产厂家信息详细信息
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:query')")
    @GetMapping(value = "/{factoryId}")
    public AjaxResult getInfo(@PathVariable("factoryId") Long factoryId)
    {
        return success(pillFactoryService.selectPillFactoryByFactoryId(factoryId));
    }

    /**
     * 新增生产厂家信息
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:add')")
    @Log(title = "生产厂家信息", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody PillFactory pillFactory)
    {
        return toAjax(pillFactoryService.insertPillFactory(pillFactory));
    }

    /**
     * 修改生产厂家信息
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:edit')")
    @Log(title = "生产厂家信息", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody PillFactory pillFactory)
    {
        return toAjax(pillFactoryService.updatePillFactory(pillFactory));
    }

    /**
     * 删除生产厂家信息
     */
    @PreAuthorize("@ss.hasPermi('pill:factory:remove')")
    @Log(title = "生产厂家信息", businessType = BusinessType.DELETE)
   @DeleteMapping("/{factoryIds}")
    public AjaxResult remove(@PathVariable Long[] factoryIds)
    {
        return toAjax(pillFactoryService.deletePillFactoryByFactoryIds(factoryIds));
    }
}

4.
package com.ruoyi.web.controller.pill;

import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.pill.domain.PillPurchase;
import com.ruoyi.pill.service.IPillPurchaseService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;

/**
 * 药品进货记录Controller
 *
 * @author wuweiqiang
 * @date 2025-05-19
 */
@RestController
@RequestMapping("/pill/purchase")
public class PillPurchaseController extends BaseController
{
    @Autowired
    private IPillPurchaseService pillPurchaseService;

    /**
     * 查询药品进货记录列表
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:list')")
    @GetMapping("/list")
    public TableDataInfo list(PillPurchase pillPurchase)
    {
        startPage();
        List<PillPurchase> list = pillPurchaseService.selectPillPurchaseList(pillPurchase);
        return getDataTable(list);
    }

    /**
     * 导出药品进货记录列表
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:export')")
    @Log(title = "药品进货记录", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PillPurchase pillPurchase)
    {
        List<PillPurchase> list = pillPurchaseService.selectPillPurchaseList(pillPurchase);
        ExcelUtil<PillPurchase> util = new ExcelUtil<PillPurchase>(PillPurchase.class);
        util.exportExcel(response, list, "药品进货记录数据");
    }

    /**
     * 获取药品进货记录详细信息
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:query')")
    @GetMapping(value = "/{purchaseId}")
    public AjaxResult getInfo(@PathVariable("purchaseId") Long purchaseId)
    {
        return success(pillPurchaseService.selectPillPurchaseByPurchaseId(purchaseId));
    }

    /**
     * 新增药品进货记录
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:add')")
    @Log(title = "药品进货记录", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody PillPurchase pillPurchase)
    {
        return toAjax(pillPurchaseService.insertPillPurchase(pillPurchase));
    }

    /**
     * 修改药品进货记录
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:edit')")
    @Log(title = "药品进货记录", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody PillPurchase pillPurchase)
    {
        return toAjax(pillPurchaseService.updatePillPurchase(pillPurchase));
    }

    /**
     * 删除药品进货记录
     */
    @PreAuthorize("@ss.hasPermi('pill:purchase:remove')")
    @Log(title = "药品进货记录", businessType = BusinessType.DELETE)
   @DeleteMapping("/{purchaseIds}")
    public AjaxResult remove(@PathVariable Long[] purchaseIds)
    {
        return toAjax(pillPurchaseService.deletePillPurchaseByPurchaseIds(purchaseIds));
    }
}

Domain
1. PillDrug
package com.ruoyi.pill.domain;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

/**
 * 药品信息对象 pill_drug
 *
 * @author wuweiqiang
 * @date 2025-05-19
 */
public class PillDrug extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 药品ID */
    private Long drugId;

    /** 药品名称 */
    @Excel(name = "药品名称")
    private String drugName;

    /** 药品编码 */
    @Excel(name = "药品编码")
    private String drugCode;

    /** 生产厂家 */
    @Excel(name = "生产厂家")
    private Long factoryId;

    /** 药品类型 */
    @Excel(name = "药品类型")
    private String drugType;

    /** 处方类型 */
    @Excel(name = "处方类型")
    private String prescriptionType;

    /** 单位 */
    private String unit;

    /** 价格 */
    @Excel(name = "价格")
    private Long price;

    /** 库存量 */
    @Excel(name = "库存量")
    private Long num;

    /** 预警值 */
    @Excel(name = "预警值")
    private Long warnValue;

    /** 换算量 */
    @Excel(name = "换算量")
    private Long conversion;

    /** 状态 */
    @Excel(name = "状态")
    private String status;

    public void setDrugId(Long drugId)
    {
        this.drugId = drugId;
    }

    public Long getDrugId()
    {
        return drugId;
    }

    public void setDrugName(String drugName)
    {
        this.drugName = drugName;
    }

    public String getDrugName()
    {
        return drugName;
    }

    public void setDrugCode(String drugCode)
    {
        this.drugCode = drugCode;
    }

    public String getDrugCode()
    {
        return drugCode;
    }

    public void setFactoryId(Long factoryId)
    {
        this.factoryId = factoryId;
    }

    public Long getFactoryId()
    {
        return factoryId;
    }

    public void setDrugType(String drugType)
    {
        this.drugType = drugType;
    }

    public String getDrugType()
    {
        return drugType;
    }

    public void setPrescriptionType(String prescriptionType)
    {
        this.prescriptionType = prescriptionType;
    }

    public String getPrescriptionType()
    {
        return prescriptionType;
    }

    public void setUnit(String unit)
    {
        this.unit = unit;
    }

    public String getUnit()
    {
        return unit;
    }

    public void setPrice(Long price)
    {
        this.price = price;
    }

    public Long getPrice()
    {
        return price;
    }

    public void setNum(Long num)
    {
        this.num = num;
    }

    public Long getNum()
    {
        return num;
    }

    public void setWarnValue(Long warnValue)
    {
        this.warnValue = warnValue;
    }

    public Long getWarnValue()
    {
        return warnValue;
    }

    public void setConversion(Long conversion)
    {
        this.conversion = conversion;
    }

    public Long getConversion()
    {
        return conversion;
    }

    public void setStatus(String status)
    {
        this.status = status;
    }

    public String getStatus()
    {
        return status;
    }

    private PillFactory pillFactory;
    public PillFactory getPillFactory()
    {
        return pillFactory;
    }
    public void setPillFactory(PillFactory pillFactory)
    {
        this.pillFactory = pillFactory;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("drugId", getDrugId())
            .append("drugName", getDrugName())
            .append("drugCode", getDrugCode())
            .append("factoryId", getFactoryId())
            .append("drugType", getDrugType())
            .append("prescriptionType", getPrescriptionType())
            .append("unit", getUnit())
            .append("price", getPrice())
            .append("num", getNum())
            .append("warnValue", getWarnValue())
            .append("conversion", getConversion())
            .append("status", getStatus())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .append("remark", getRemark())
                .append("pillFactory", getPillFactory())
            .toString();
    }
}

2.
package com.ruoyi.pill.domain;

import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

/**
 * 药品有效期预警，用于监控药品保质期对象 pill_expiry_warning
 *
 * @author wuweiqiang
 * @date 2025-05-20
 */
public class PillExpiryWarning extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 是否只查询15天内过期 */
    private Boolean onlyExpire;

    public Boolean getOnlyExpire() {
        return onlyExpire;
    }

    public void setOnlyExpire(Boolean onlyExpire) {
        this.onlyExpire = onlyExpire;
    }

    /** 主键ID */
    private Long warningId;

    /** 药品ID（关联pill_drug.drug_id） */
    @Excel(name = "药品ID", readConverterExp = "关=联pill_drug.drug_id")
    private Long drugId;

    /** 厂家ID（关联pill_factory.factory_id） */
    @Excel(name = "厂家ID", readConverterExp = "关=联pill_factory.factory_id")
    private Long factoryId;

    /** 药品生产日期 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "药品生产日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date productionDate;

    /** 保质期（天） */
    @Excel(name = "保质期", readConverterExp = "天=")
    private Long validityPeriod;

    /** 进货单ID（关联pill_purchase.purchase_id） */
    @Excel(name = "进货单ID", readConverterExp = "关=联pill_purchase.purchase_id")
    private Long purchaseId;

    /** 库存数量 */
    @Excel(name = "库存数量")
    private Long stock;

    /** 药品名称（来自pill_drug） */
    @Excel(name = "药品名称")
    private String drugName;

    /** 厂家名称（来自pill_factory） */
    @Excel(name = "生产厂家")
    private String factoryName;

    /** 进货时间（来自pill_purchase） */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "进货时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date purchaseTime;

    /** 距离过期天数 */
    @Excel(name = "距离过期天数")
    private Long daysToExpire;

    public void setWarningId(Long warningId)
    {
        this.warningId = warningId;
    }

    public Long getWarningId()
    {
        return warningId;
    }

    public void setDrugId(Long drugId)
    {
        this.drugId = drugId;
    }

    public Long getDrugId()
    {
        return drugId;
    }

    public void setFactoryId(Long factoryId)
    {
        this.factoryId = factoryId;
    }

    public Long getFactoryId()
    {
        return factoryId;
    }

    public void setProductionDate(Date productionDate)
    {
        this.productionDate = productionDate;
    }

    public Date getProductionDate()
    {
        return productionDate;
    }

    public void setValidityPeriod(Long validityPeriod)
    {
        this.validityPeriod = validityPeriod;
    }

    public Long getValidityPeriod()
    {
        return validityPeriod;
    }

    public void setPurchaseId(Long purchaseId)
    {
        this.purchaseId = purchaseId;
    }

    public Long getPurchaseId()
    {
        return purchaseId;
    }

    public void setStock(Long stock)
    {
        this.stock = stock;
    }

    public Long getStock()
    {
        return stock;
    }

    public String getDrugName() {
        return drugName;
    }

    public void setDrugName(String drugName) {
        this.drugName = drugName;
    }

    public String getFactoryName() {
        return factoryName;
    }

    public void setFactoryName(String factoryName) {
        this.factoryName = factoryName;
    }

    public Date getPurchaseTime() {
        return purchaseTime;
    }

    public void setPurchaseTime(Date purchaseTime) {
        this.purchaseTime = purchaseTime;
    }

    public Long getDaysToExpire() {
        return daysToExpire;
    }

    public void setDaysToExpire(Long daysToExpire) {
        this.daysToExpire = daysToExpire;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
                .append("warningId", getWarningId())
                .append("drugId", getDrugId())
                .append("factoryId", getFactoryId())
                .append("productionDate", getProductionDate())
                .append("validityPeriod", getValidityPeriod())
                .append("purchaseId", getPurchaseId())
                .append("stock", getStock())
                .append("drugName", getDrugName())
                .append("factoryName", getFactoryName())
                .append("purchaseTime", getPurchaseTime())
                .append("daysToExpire", getDaysToExpire())
                .append("createTime", getCreateTime())
                .toString();
    }
}

3.
package com.ruoyi.pill.domain;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

/**
 * 生产厂家信息对象 pill_factory
 * 
 * @author wuweiqiang
 * @date 2025-05-18
 */
public class PillFactory extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 厂家ID */
    private Long factoryId;

    /** 厂家名称 */
    @Excel(name = "厂家名称")
    private String factoryName;

    /** 厂家编码 */
    @Excel(name = "厂家编码")
    private String factoryCode;

    /** 联系人 */
    @Excel(name = "联系人")
    private String contact;

    /** 电话 */
    @Excel(name = "电话")
    private String phone;

    /** 关键字 */
    @Excel(name = "关键字")
    private String keyword;

    /** 状态 */
    @Excel(name = "状态")
    private String status;

    public void setFactoryId(Long factoryId) 
    {
        this.factoryId = factoryId;
    }

    public Long getFactoryId() 
    {
        return factoryId;
    }

    public void setFactoryName(String factoryName) 
    {
        this.factoryName = factoryName;
    }

    public String getFactoryName() 
    {
        return factoryName;
    }

    public void setFactoryCode(String factoryCode) 
    {
        this.factoryCode = factoryCode;
    }

    public String getFactoryCode() 
    {
        return factoryCode;
    }

    public void setContact(String contact) 
    {
        this.contact = contact;
    }

    public String getContact() 
    {
        return contact;
    }

    public void setPhone(String phone) 
    {
        this.phone = phone;
    }

    public String getPhone() 
    {
        return phone;
    }

    public void setKeyword(String keyword) 
    {
        this.keyword = keyword;
    }

    public String getKeyword() 
    {
        return keyword;
    }

    public void setStatus(String status) 
    {
        this.status = status;
    }

    public String getStatus() 
    {
        return status;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("factoryId", getFactoryId())
            .append("factoryName", getFactoryName())
            .append("factoryCode", getFactoryCode())
            .append("contact", getContact())
            .append("phone", getPhone())
            .append("keyword", getKeyword())
            .append("status", getStatus())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .append("remark", getRemark())
            .toString();
    }
}

4.
package com.ruoyi.pill.domain;

import java.math.BigDecimal;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

public class PillPurchase extends BaseEntity {
    private static final long serialVersionUID = 1L;

    private Long purchaseId;

    private String drugName;

    private String factoryName;

    private Long drugId;

    private Long factoryId;

    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "进货时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date purchaseTime;

    @Excel(name = "进货单价")
    private BigDecimal purchasePrice;

    @Excel(name = "进货数量")
    private Long purchaseNum;

    public Long getPurchaseId() {
        return purchaseId;
    }

    public void setPurchaseId(Long purchaseId) {
        this.purchaseId = purchaseId;
    }

    public String getDrugName() {
        return drugName;
    }

    public void setDrugName(String drugName) {
        this.drugName = drugName;
    }

    public String getFactoryName() {
        return factoryName;
    }

    public void setFactoryName(String factoryName) {
        this.factoryName = factoryName;
    }

    public Long getDrugId() {
        return drugId;
    }

    public void setDrugId(Long drugId) {
        this.drugId = drugId;
    }

    public Long getFactoryId() {
        return factoryId;
    }

    public void setFactoryId(Long factoryId) {
        this.factoryId = factoryId;
    }

    public Date getPurchaseTime() {
        return purchaseTime;
    }

    public void setPurchaseTime(Date purchaseTime) {
        this.purchaseTime = purchaseTime;
    }

    public BigDecimal getPurchasePrice() {
        return purchasePrice;
    }

    public void setPurchasePrice(BigDecimal purchasePrice) {
        this.purchasePrice = purchasePrice;
    }

    public Long getPurchaseNum() {
        return purchaseNum;
    }

    public void setPurchaseNum(Long purchaseNum) {
        this.purchaseNum = purchaseNum;
    }

    // getter setter 略，保持和你已有一致
}

mapper
1.
package com.ruoyi.pill.mapper;

import java.util.List;
import com.ruoyi.pill.domain.PillDrug;

/**
 * 药品信息Mapper接口
 * 
 * @author wuweiqiang
 * @date 2025-05-19
 */
public interface PillDrugMapper 
{
    /**
     * 查询药品信息
     * 
     * @param drugId 药品信息主键
     * @return 药品信息
     */
    public PillDrug selectPillDrugByDrugId(Long drugId);

    /**
     * 查询药品信息列表
     * 
     * @param pillDrug 药品信息
     * @return 药品信息集合
     */
    public List<PillDrug> selectPillDrugList(PillDrug pillDrug);

    /**
     * 新增药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    public int insertPillDrug(PillDrug pillDrug);

    /**
     * 修改药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    public int updatePillDrug(PillDrug pillDrug);

    /**
     * 删除药品信息
     * 
     * @param drugId 药品信息主键
     * @return 结果
     */
    public int deletePillDrugByDrugId(Long drugId);

    /**
     * 批量删除药品信息
     * 
     * @param drugIds 需要删除的数据主键集合
     * @return 结果
     */
    public int deletePillDrugByDrugIds(Long[] drugIds);
}

2.
package com.ruoyi.pill.mapper;

import java.util.List;
import com.ruoyi.pill.domain.PillExpiryWarning;

/**
 * 药品有效期预警，用于监控药品保质期Mapper接口
 * 
 * @author wuweiqiang
 * @date 2025-05-20
 */
public interface PillExpiryWarningMapper 
{
    /**
     * 查询药品有效期预警，用于监控药品保质期
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 药品有效期预警，用于监控药品保质期
     */
    public PillExpiryWarning selectPillExpiryWarningByWarningId(Long warningId);

    /**
     * 查询药品有效期预警，用于监控药品保质期列表
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 药品有效期预警，用于监控药品保质期集合
     */
    public List<PillExpiryWarning> selectPillExpiryWarningList(PillExpiryWarning pillExpiryWarning);

    /**
     * 新增药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    public int insertPillExpiryWarning(PillExpiryWarning pillExpiryWarning);

    /**
     * 修改药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    public int updatePillExpiryWarning(PillExpiryWarning pillExpiryWarning);

    /**
     * 删除药品有效期预警，用于监控药品保质期
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 结果
     */
    public int deletePillExpiryWarningByWarningId(Long warningId);

    /**
     * 批量删除药品有效期预警，用于监控药品保质期
     * 
     * @param warningIds 需要删除的数据主键集合
     * @return 结果
     */
    public int deletePillExpiryWarningByWarningIds(Long[] warningIds);
}

3.
package com.ruoyi.pill.mapper;

import java.util.List;
import com.ruoyi.pill.domain.PillFactory;
import org.apache.ibatis.annotations.Mapper;

/**
 * 生产厂家信息Mapper接口
 *
 * @author wuweiqiang
 * @date 2025-05-18
 */
@Mapper
public interface PillFactoryMapper
{
    /**
     * 查询生产厂家信息
     *
     * @param factoryId 生产厂家信息主键
     * @return 生产厂家信息
     */
    public PillFactory selectPillFactoryByFactoryId(Long factoryId);

    /**
     * 查询生产厂家信息列表
     *
     * @param pillFactory 生产厂家信息
     * @return 生产厂家信息集合
     */
    public List<PillFactory> selectPillFactoryList(PillFactory pillFactory);

    /**
     * 新增生产厂家信息
     *
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    public int insertPillFactory(PillFactory pillFactory);

    /**
     * 修改生产厂家信息
     *
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    public int updatePillFactory(PillFactory pillFactory);

    /**
     * 删除生产厂家信息
     *
     * @param factoryId 生产厂家信息主键
     * @return 结果
     */
    public int deletePillFactoryByFactoryId(Long factoryId);

    /**
     * 批量删除生产厂家信息
     *
     * @param factoryIds 需要删除的数据主键集合
     * @return 结果
     */
    public int deletePillFactoryByFactoryIds(Long[] factoryIds);
}

4.
package com.ruoyi.pill.mapper;

import java.util.List;
import com.ruoyi.pill.domain.PillPurchase;

public interface PillPurchaseMapper {

    PillPurchase selectPillPurchaseByPurchaseId(Long purchaseId);

    List<PillPurchase> selectPillPurchaseList(PillPurchase pillPurchase);

    int insertPillPurchase(PillPurchase pillPurchase);

    int updatePillPurchase(PillPurchase pillPurchase);

    int deletePillPurchaseByPurchaseId(Long purchaseId);

    int deletePillPurchaseByPurchaseIds(Long[] purchaseIds);

    Integer selectCurrentInventoryByDrugId(Long drugId);

    int updateInventoryByDrugId(java.util.Map<String,Object> params);
}

service
impl
1.
package com.ruoyi.pill.service.impl;

import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.pill.mapper.PillDrugMapper;
import com.ruoyi.pill.domain.PillDrug;
import com.ruoyi.pill.service.IPillDrugService;

/**
 * 药品信息Service业务层处理
 * 
 * @author wuweiqiang
 * @date 2025-05-19
 */
@Service
public class PillDrugServiceImpl implements IPillDrugService 
{
    @Autowired
    private PillDrugMapper pillDrugMapper;

    /**
     * 查询药品信息
     * 
     * @param drugId 药品信息主键
     * @return 药品信息
     */
    @Override
    public PillDrug selectPillDrugByDrugId(Long drugId)
    {
        return pillDrugMapper.selectPillDrugByDrugId(drugId);
    }

    /**
     * 查询药品信息列表
     * 
     * @param pillDrug 药品信息
     * @return 药品信息
     */
    @Override
    public List<PillDrug> selectPillDrugList(PillDrug pillDrug)
    {
        return pillDrugMapper.selectPillDrugList(pillDrug);
    }

    /**
     * 新增药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    @Override
    public int insertPillDrug(PillDrug pillDrug)
    {
        pillDrug.setCreateTime(DateUtils.getNowDate());
        return pillDrugMapper.insertPillDrug(pillDrug);
    }

    /**
     * 修改药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    @Override
    public int updatePillDrug(PillDrug pillDrug)
    {
        pillDrug.setUpdateTime(DateUtils.getNowDate());
        return pillDrugMapper.updatePillDrug(pillDrug);
    }

    /**
     * 批量删除药品信息
     * 
     * @param drugIds 需要删除的药品信息主键
     * @return 结果
     */
    @Override
    public int deletePillDrugByDrugIds(Long[] drugIds)
    {
        return pillDrugMapper.deletePillDrugByDrugIds(drugIds);
    }

    /**
     * 删除药品信息信息
     * 
     * @param drugId 药品信息主键
     * @return 结果
     */
    @Override
    public int deletePillDrugByDrugId(Long drugId)
    {
        return pillDrugMapper.deletePillDrugByDrugId(drugId);
    }
}

2.
package com.ruoyi.pill.service.impl;

import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.pill.mapper.PillExpiryWarningMapper;
import com.ruoyi.pill.domain.PillExpiryWarning;
import com.ruoyi.pill.service.IPillExpiryWarningService;

/**
 * 药品有效期预警，用于监控药品保质期Service业务层处理
 * 
 * @author wuweiqiang
 * @date 2025-05-20
 */
@Service
public class PillExpiryWarningServiceImpl implements IPillExpiryWarningService 
{
    @Autowired
    private PillExpiryWarningMapper pillExpiryWarningMapper;

    /**
     * 查询药品有效期预警，用于监控药品保质期
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 药品有效期预警，用于监控药品保质期
     */
    @Override
    public PillExpiryWarning selectPillExpiryWarningByWarningId(Long warningId)
    {
        return pillExpiryWarningMapper.selectPillExpiryWarningByWarningId(warningId);
    }

    /**
     * 查询药品有效期预警，用于监控药品保质期列表
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 药品有效期预警，用于监控药品保质期
     */
    @Override
    public List<PillExpiryWarning> selectPillExpiryWarningList(PillExpiryWarning pillExpiryWarning)
    {
        return pillExpiryWarningMapper.selectPillExpiryWarningList(pillExpiryWarning);
    }

    /**
     * 新增药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    @Override
    public int insertPillExpiryWarning(PillExpiryWarning pillExpiryWarning)
    {
        pillExpiryWarning.setCreateTime(DateUtils.getNowDate());
        return pillExpiryWarningMapper.insertPillExpiryWarning(pillExpiryWarning);
    }

    /**
     * 修改药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    @Override
    public int updatePillExpiryWarning(PillExpiryWarning pillExpiryWarning)
    {
        return pillExpiryWarningMapper.updatePillExpiryWarning(pillExpiryWarning);
    }

    /**
     * 批量删除药品有效期预警，用于监控药品保质期
     * 
     * @param warningIds 需要删除的药品有效期预警，用于监控药品保质期主键
     * @return 结果
     */
    @Override
    public int deletePillExpiryWarningByWarningIds(Long[] warningIds)
    {
        return pillExpiryWarningMapper.deletePillExpiryWarningByWarningIds(warningIds);
    }

    /**
     * 删除药品有效期预警，用于监控药品保质期信息
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 结果
     */
    @Override
    public int deletePillExpiryWarningByWarningId(Long warningId)
    {
        return pillExpiryWarningMapper.deletePillExpiryWarningByWarningId(warningId);
    }
}

3.
package com.ruoyi.pill.service.impl;

import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.pill.mapper.PillFactoryMapper;
import com.ruoyi.pill.domain.PillFactory;
import com.ruoyi.pill.service.IPillFactoryService;

/**
 * 生产厂家信息Service业务层处理
 *
 * @author wuweiqiang
 * @date 2025-05-18
 */
@Service
public class PillFactoryServiceImpl implements IPillFactoryService
{
    @Autowired

    private PillFactoryMapper pillFactoryMapper;

    /**
     * 查询生产厂家信息
     *
     * @param factoryId 生产厂家信息主键
     * @return 生产厂家信息
     */
    @Override
    public PillFactory selectPillFactoryByFactoryId(Long factoryId)
    {
        return pillFactoryMapper.selectPillFactoryByFactoryId(factoryId);
    }

    /**
     * 查询生产厂家信息列表
     *
     * @param pillFactory 生产厂家信息
     * @return 生产厂家信息
     */
    @Override
    public List<PillFactory> selectPillFactoryList(PillFactory pillFactory)
    {
        return pillFactoryMapper.selectPillFactoryList(pillFactory);
    }

    /**
     * 新增生产厂家信息
     *
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    @Override
    public int insertPillFactory(PillFactory pillFactory)
    {
        pillFactory.setCreateTime(DateUtils.getNowDate());
        return pillFactoryMapper.insertPillFactory(pillFactory);
    }

    /**
     * 修改生产厂家信息
     *
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    @Override
    public int updatePillFactory(PillFactory pillFactory)
    {
        pillFactory.setUpdateTime(DateUtils.getNowDate());
        return pillFactoryMapper.updatePillFactory(pillFactory);
    }

    /**
     * 批量删除生产厂家信息
     *
     * @param factoryIds 需要删除的生产厂家信息主键
     * @return 结果
     */
    @Override
    public int deletePillFactoryByFactoryIds(Long[] factoryIds)
    {
        return pillFactoryMapper.deletePillFactoryByFactoryIds(factoryIds);
    }

    /**
     * 删除生产厂家信息信息
     *
     * @param factoryId 生产厂家信息主键
     * @return 结果
     */
    @Override
    public int deletePillFactoryByFactoryId(Long factoryId)
    {
        return pillFactoryMapper.deletePillFactoryByFactoryId(factoryId);
    }
}

4.
package com.ruoyi.pill.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.pill.domain.PillPurchase;
import com.ruoyi.pill.mapper.PillPurchaseMapper;
import com.ruoyi.pill.service.IPillPurchaseService;

@Service
public class PillPurchaseServiceImpl implements IPillPurchaseService {

    @Autowired
    private PillPurchaseMapper pillPurchaseMapper;

    /**
     * 插入一条进货记录，并更新库存（增加）
     */
    @Override
    @Transactional
    public int insertPillPurchase(PillPurchase pillPurchase) {
        pillPurchase.setCreateTime(DateUtils.getNowDate());
        int result = pillPurchaseMapper.insertPillPurchase(pillPurchase);
        if(result > 0 && pillPurchase.getDrugId() != null && pillPurchase.getPurchaseNum() != null) {
            adjustInventory(pillPurchase.getDrugId(), pillPurchase.getPurchaseNum());
        }
        return result;
    }

    /**
     * 更新进货记录，并根据差额调整库存
     */
    @Override
    @Transactional
    public int updatePillPurchase(PillPurchase pillPurchase) {
        PillPurchase oldPurchase = pillPurchaseMapper.selectPillPurchaseByPurchaseId(pillPurchase.getPurchaseId());
        if(oldPurchase == null) {
            throw new RuntimeException("进货记录不存在，无法修改库存");
        }

        pillPurchase.setUpdateTime(DateUtils.getNowDate());
        int result = pillPurchaseMapper.updatePillPurchase(pillPurchase);

        if(result > 0) {
            Long drugId = pillPurchase.getDrugId();
            Long oldNum = oldPurchase.getPurchaseNum() == null ? 0L : oldPurchase.getPurchaseNum();
            Long newNum = pillPurchase.getPurchaseNum() == null ? 0L : pillPurchase.getPurchaseNum();

            long diff = newNum - oldNum;
            if(drugId != null && diff != 0) {
                adjustInventory(drugId, diff);
            }
        }

        return result;
    }

    /**
     * 批量删除进货记录，减少库存
     */
    @Override
    @Transactional
    public int deletePillPurchaseByPurchaseIds(Long[] purchaseIds) {
        if(purchaseIds == null || purchaseIds.length == 0) {
            return 0;
        }

        for(Long id : purchaseIds) {
            PillPurchase purchase = pillPurchaseMapper.selectPillPurchaseByPurchaseId(id);
            if(purchase != null && purchase.getDrugId() != null && purchase.getPurchaseNum() != null) {
                adjustInventory(purchase.getDrugId(), -purchase.getPurchaseNum());
            }
        }

        return pillPurchaseMapper.deletePillPurchaseByPurchaseIds(purchaseIds);
    }

    /**
     * 删除单条进货记录，减少库存
     */
    @Override
    @Transactional
    public int deletePillPurchaseByPurchaseId(Long purchaseId) {
        PillPurchase purchase = pillPurchaseMapper.selectPillPurchaseByPurchaseId(purchaseId);
        if(purchase == null) {
            return 0;
        }
        int result = pillPurchaseMapper.deletePillPurchaseByPurchaseId(purchaseId);
        if(result > 0 && purchase.getDrugId() != null && purchase.getPurchaseNum() != null) {
            adjustInventory(purchase.getDrugId(), -purchase.getPurchaseNum());
        }
        return result;
    }

    /**
     * 核心库存调整逻辑
     * 注意库存不可为负
     */
    private void adjustInventory(Long drugId, Long diffNum) {
        if(drugId == null) {
            throw new IllegalArgumentException("drugId不能为空");
        }
        if(diffNum == null || diffNum == 0) {
            return;
        }

        Integer currentInventory = pillPurchaseMapper.selectCurrentInventoryByDrugId(drugId);
        long current = currentInventory == null ? 0L : currentInventory.longValue();

        long newInventory = current + diffNum;
        if(newInventory < 0) {
            newInventory = 0L; // 防止库存为负
        }

        Map<String,Object> params = new HashMap<>();
        params.put("drugId", drugId);
        params.put("inventory", newInventory);

        pillPurchaseMapper.updateInventoryByDrugId(params);
    }

    @Override
    public List<PillPurchase> selectPillPurchaseList(PillPurchase pillPurchase) {
        return pillPurchaseMapper.selectPillPurchaseList(pillPurchase);
    }

    @Override
    public PillPurchase selectPillPurchaseByPurchaseId(Long purchaseId) {
        return pillPurchaseMapper.selectPillPurchaseByPurchaseId(purchaseId);
    }

    @Override
    public List<PillPurchase> selectPillPurchaseList(String drugName, String factoryName) {
        PillPurchase pillPurchase = new PillPurchase();
        pillPurchase.setDrugName(drugName);
        pillPurchase.setFactoryName(factoryName);
        return pillPurchaseMapper.selectPillPurchaseList(pillPurchase);
    }

}

5.1. IPillDrugService
package com.ruoyi.pill.service;

import java.util.List;
import com.ruoyi.pill.domain.PillDrug;

/**
 * 药品信息Service接口
 * 
 * @author wuweiqiang
 * @date 2025-05-19
 */
public interface IPillDrugService 
{
    /**
     * 查询药品信息
     * 
     * @param drugId 药品信息主键
     * @return 药品信息
     */
    public PillDrug selectPillDrugByDrugId(Long drugId);

    /**
     * 查询药品信息列表
     * 
     * @param pillDrug 药品信息
     * @return 药品信息集合
     */
    public List<PillDrug> selectPillDrugList(PillDrug pillDrug);

    /**
     * 新增药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    public int insertPillDrug(PillDrug pillDrug);

    /**
     * 修改药品信息
     * 
     * @param pillDrug 药品信息
     * @return 结果
     */
    public int updatePillDrug(PillDrug pillDrug);

    /**
     * 批量删除药品信息
     * 
     * @param drugIds 需要删除的药品信息主键集合
     * @return 结果
     */
    public int deletePillDrugByDrugIds(Long[] drugIds);

    /**
     * 删除药品信息信息
     * 
     * @param drugId 药品信息主键
     * @return 结果
     */
    public int deletePillDrugByDrugId(Long drugId);
}

5.2
package com.ruoyi.pill.service;

import java.util.List;
import com.ruoyi.pill.domain.PillExpiryWarning;

/**
 * 药品有效期预警，用于监控药品保质期Service接口
 * 
 * @author wuweiqiang
 * @date 2025-05-20
 */
public interface IPillExpiryWarningService 
{
    /**
     * 查询药品有效期预警，用于监控药品保质期
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 药品有效期预警，用于监控药品保质期
     */
    public PillExpiryWarning selectPillExpiryWarningByWarningId(Long warningId);

    /**
     * 查询药品有效期预警，用于监控药品保质期列表
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 药品有效期预警，用于监控药品保质期集合
     */
    public List<PillExpiryWarning> selectPillExpiryWarningList(PillExpiryWarning pillExpiryWarning);

    /**
     * 新增药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    public int insertPillExpiryWarning(PillExpiryWarning pillExpiryWarning);

    /**
     * 修改药品有效期预警，用于监控药品保质期
     * 
     * @param pillExpiryWarning 药品有效期预警，用于监控药品保质期
     * @return 结果
     */
    public int updatePillExpiryWarning(PillExpiryWarning pillExpiryWarning);

    /**
     * 批量删除药品有效期预警，用于监控药品保质期
     * 
     * @param warningIds 需要删除的药品有效期预警，用于监控药品保质期主键集合
     * @return 结果
     */
    public int deletePillExpiryWarningByWarningIds(Long[] warningIds);

    /**
     * 删除药品有效期预警，用于监控药品保质期信息
     * 
     * @param warningId 药品有效期预警，用于监控药品保质期主键
     * @return 结果
     */
    public int deletePillExpiryWarningByWarningId(Long warningId);
}

5.3
package com.ruoyi.pill.service;

import java.util.List;
import com.ruoyi.pill.domain.PillFactory;

/**
 * 生产厂家信息Service接口
 * 
 * @author wuweiqiang
 * @date 2025-05-18
 */
public interface IPillFactoryService 
{
    /**
     * 查询生产厂家信息
     * 
     * @param factoryId 生产厂家信息主键
     * @return 生产厂家信息
     */
    public PillFactory selectPillFactoryByFactoryId(Long factoryId);

    /**
     * 查询生产厂家信息列表
     * 
     * @param pillFactory 生产厂家信息
     * @return 生产厂家信息集合
     */
    public List<PillFactory> selectPillFactoryList(PillFactory pillFactory);

    /**
     * 新增生产厂家信息
     * 
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    public int insertPillFactory(PillFactory pillFactory);

    /**
     * 修改生产厂家信息
     * 
     * @param pillFactory 生产厂家信息
     * @return 结果
     */
    public int updatePillFactory(PillFactory pillFactory);

    /**
     * 批量删除生产厂家信息
     * 
     * @param factoryIds 需要删除的生产厂家信息主键集合
     * @return 结果
     */
    public int deletePillFactoryByFactoryIds(Long[] factoryIds);

    /**
     * 删除生产厂家信息信息
     * 
     * @param factoryId 生产厂家信息主键
     * @return 结果
     */
    public int deletePillFactoryByFactoryId(Long factoryId);
}

5.4
package com.ruoyi.pill.service;

import java.util.List;
import com.ruoyi.pill.domain.PillPurchase;
import io.lettuce.core.dynamic.annotation.Param;

/**
 * 药品进货记录Service接口
 *
 * @author wuweiqiang
 * @date 2025-05-19
 */
public interface IPillPurchaseService
{
    List<PillPurchase> selectPillPurchaseList(@Param("drugName") String drugName,
                                              @Param("factoryName") String factoryName);
    /**
     * 查询药品进货记录
     *
     * @param purchaseId 药品进货记录主键
     * @return 药品进货记录
     */
    public PillPurchase selectPillPurchaseByPurchaseId(Long purchaseId);

    /**
     * 查询药品进货记录列表
     *
     * @param pillPurchase 药品进货记录
     * @return 药品进货记录集合
     */
    public List<PillPurchase> selectPillPurchaseList(PillPurchase pillPurchase);

    /**
     * 新增药品进货记录
     *
     * @param pillPurchase 药品进货记录
     * @return 结果
     */
    public int insertPillPurchase(PillPurchase pillPurchase);

    /**
     * 修改药品进货记录
     *
     * @param pillPurchase 药品进货记录
     * @return 结果
     */
    public int updatePillPurchase(PillPurchase pillPurchase);

    /**
     * 批量删除药品进货记录
     *
     * @param purchaseIds 需要删除的药品进货记录主键集合
     * @return 结果
     */
    public int deletePillPurchaseByPurchaseIds(Long[] purchaseIds);

    /**
     * 删除药品进货记录信息
     *
     * @param purchaseId 药品进货记录主键
     * @return 结果
     */
    public int deletePillPurchaseByPurchaseId(Long purchaseId);

}

resource
1.
<?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.pill.mapper.PillDrugMapper">

    <resultMap type="PillDrug" id="PillDrugResult">
        <result property="drugId"    column="drug_id"    />
        <result property="drugName"    column="drug_name"    />
        <result property="drugCode"    column="drug_code"    />
        <result property="factoryId"    column="factory_id"    />
        <result property="drugType"    column="drug_type"    />
        <result property="prescriptionType"    column="prescription_type"    />
        <result property="unit"    column="unit"    />
        <result property="price"    column="price"    />
        <result property="num"    column="num"    />
        <result property="warnValue"    column="warn_value"    />
        <result property="conversion"    column="conversion"    />
        <result property="status"    column="status"    />
        <result property="createBy"    column="create_by"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
        <result property="updateTime"    column="update_time"    />
        <result property="remark"    column="remark"    />
        <association property="pillFactory" javaType="pillFactory"
                     resultMap="com.ruoyi.pill.mapper.PillFactoryMapper.PillFactoryResult"></association>
    </resultMap>

    <sql id="selectPillDrugVo">
        select pd.*,pf.* from pill_drug pd left join pill_factory pf on pd.factory_id =                                                                 pf.factory_id
    </sql>

    <select id="selectPillDrugList" parameterType="PillDrug" resultMap="PillDrugResult">
        <include refid="selectPillDrugVo"/>
        <where>
            <if test="drugName != null and drugName != ''">
                and pd.drug_name like concat('%', #{drugName}, '%')
            </if>
            <if test="factoryId != null ">
                and pd.factory_id like concat('%', #{factoryId}, '%')
            </if>
            <if test="pillFactory != null and pillFactory.factoryName != null and pillFactory.factoryName != ''">
                and pf.factory_name like concat('%', #{pillFactory.factoryName}, '%')
            </if>
            <if test="drugType != null and drugType != ''">
                and pd.drug_type = #{drugType}
            </if>
            <if test="prescriptionType != null and prescriptionType != ''">
                and pd.prescription_type = #{prescriptionType}
            </if>
            <if test="status != null and status != ''">
                and pd.status = #{status}
            </if>
        </where>
    </select>

    <select id="selectPillDrugByDrugId" parameterType="Long" resultMap="PillDrugResult">
        <include refid="selectPillDrugVo"/>
        where pd.drug_id = #{drugId}
    </select>

    <insert id="insertPillDrug" parameterType="PillDrug" useGeneratedKeys="true" keyProperty="drugId">
        insert into pill_drug
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="drugName != null and drugName != ''">drug_name,</if>
            <if test="drugCode != null">drug_code,</if>
            <if test="factoryId != null">factory_id,</if>
            <if test="drugType != null and drugType != ''">drug_type,</if>
            <if test="prescriptionType != null">prescription_type,</if>
            <if test="unit != null">unit,</if>
            <if test="price != null">price,</if>
            <if test="num != null">num,</if>
            <if test="warnValue != null">warn_value,</if>
            <if test="conversion != null">conversion,</if>
            <if test="status != null">status,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="drugName != null and drugName != ''">#{drugName},</if>
            <if test="drugCode != null">#{drugCode},</if>
            <if test="factoryId != null">#{factoryId},</if>
            <if test="drugType != null and drugType != ''">#{drugType},</if>
            <if test="prescriptionType != null">#{prescriptionType},</if>
            <if test="unit != null">#{unit},</if>
            <if test="price != null">#{price},</if>
            <if test="num != null">#{num},</if>
            <if test="warnValue != null">#{warnValue},</if>
            <if test="conversion != null">#{conversion},</if>
            <if test="status != null">#{status},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="remark != null">#{remark},</if>
         </trim>
    </insert>

    <update id="updatePillDrug" parameterType="PillDrug">
        update pill_drug
        <trim prefix="SET" suffixOverrides=",">
            <if test="drugName != null and drugName != ''">drug_name = #{drugName},</if>
            <if test="drugCode != null">drug_code = #{drugCode},</if>
            <if test="factoryId != null">factory_id = #{factoryId},</if>
            <if test="drugType != null and drugType != ''">drug_type = #{drugType},</if>
            <if test="prescriptionType != null">prescription_type = #{prescriptionType},</if>
            <if test="unit != null">unit = #{unit},</if>
            <if test="price != null">price = #{price},</if>
            <if test="num != null">num = #{num},</if>
            <if test="warnValue != null">warn_value = #{warnValue},</if>
            <if test="conversion != null">conversion = #{conversion},</if>
            <if test="status != null">status = #{status},</if>
            <if test="createBy != null">create_by = #{createBy},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where drug_id = #{drugId}
    </update>

    <delete id="deletePillDrugByDrugId" parameterType="Long">
        delete from pill_drug where drug_id = #{drugId}
    </delete>

    <delete id="deletePillDrugByDrugIds" parameterType="String">
        delete from pill_drug where drug_id in
        <foreach item="drugId" collection="array" open="(" separator="," close=")">
            #{drugId}
        </foreach>
    </delete>
</mapper>

2.
<?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.pill.mapper.PillExpiryWarningMapper">

    <resultMap id="PillExpiryWarningResult" type="com.ruoyi.pill.domain.PillExpiryWarning">
        <result property="warningId"    column="warning_id"    />
        <result property="drugId"    column="drug_id"    />
        <result property="factoryId"    column="factory_id"    />
        <result property="productionDate"    column="production_date"    />
        <result property="validityPeriod"    column="validity_period"    />
        <result property="purchaseId"    column="purchase_id"    />
        <result property="stock"    column="stock"    />
        <result property="createTime"    column="create_time"    />
        <result property="drugName" column="drug_name"/>
        <result property="factoryName" column="factory_name"/>
        <result property="purchaseTime" column="purchase_time"/>
        <result property="daysToExpire" column="days_to_expire"/>
    </resultMap>

    <sql id="BaseSelectSql">
        select
            pe.warning_id,
            pe.drug_id,
            pe.factory_id,
            pe.production_date,
            pe.validity_period,
            pe.purchase_id,
            pd.num as stock,
            pe.create_time,
            pd.drug_name,
            pf.factory_name,
            pp.purchase_time,
            datediff(date_add(pe.production_date, interval pe.validity_period day), curdate()) as days_to_expire
        from pill_expiry_warning pe
                 LEFT JOIN pill_drug pd ON pe.drug_id = pd.drug_id
                 LEFT JOIN pill_factory pf ON pe.factory_id = pf.factory_id
                 LEFT JOIN pill_purchase pp ON pe.purchase_id = pp.purchase_id
    </sql>

    <select id="selectPillExpiryWarningList" parameterType="com.ruoyi.pill.domain.PillExpiryWarning" resultMap="PillExpiryWarningResult">
        <include refid="BaseSelectSql"/>
        <where>
            <if test="drugId != null"> and pe.drug_id = #{drugId}</if>
            <if test="factoryId != null"> and pe.factory_id = #{factoryId}</if>
            <if test="productionDate != null"> and pe.production_date = #{productionDate}</if>
            <if test="validityPeriod != null"> and pe.validity_period = #{validityPeriod}</if>
            <if test="purchaseId != null"> and pe.purchase_id = #{purchaseId}</if>
            <!-- 只有在onlyExpire为true时才增加过期数据过滤 -->
            <if test="onlyExpire != null and onlyExpire == true">
                AND datediff(date_add(pe.production_date, interval pe.validity_period day), curdate()) &lt;= 15
            </if>
        </where>
    </select>

    <select id="selectPillExpiryWarningByWarningId" parameterType="Long" resultMap="PillExpiryWarningResult">
        <include refid="BaseSelectSql"/>
        where pe.warning_id = #{warningId}
    </select>

    <insert id="insertPillExpiryWarning" parameterType="com.ruoyi.pill.domain.PillExpiryWarning" useGeneratedKeys="true" keyProperty="warningId">
        insert into pill_expiry_warning
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="drugId != null">drug_id,</if>
            <if test="factoryId != null">factory_id,</if>
            <if test="productionDate != null">production_date,</if>
            <if test="validityPeriod != null">validity_period,</if>
            <if test="purchaseId != null">purchase_id,</if>
            <if test="stock != null">stock,</if>
            <if test="createTime != null">create_time,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="drugId != null">#{drugId},</if>
            <if test="factoryId != null">#{factoryId},</if>
            <if test="productionDate != null">#{productionDate},</if>
            <if test="validityPeriod != null">#{validityPeriod},</if>
            <if test="purchaseId != null">#{purchaseId},</if>
            <if test="stock != null">#{stock},</if>
            <if test="createTime != null">#{createTime},</if>
        </trim>
    </insert>

    <update id="updatePillExpiryWarning" parameterType="com.ruoyi.pill.domain.PillExpiryWarning">
        update pill_expiry_warning
        <set>
            <if test="drugId != null">drug_id = #{drugId},</if>
            <if test="factoryId != null">factory_id = #{factoryId},</if>
            <if test="productionDate != null">production_date = #{productionDate},</if>
            <if test="validityPeriod != null">validity_period = #{validityPeriod},</if>
            <if test="purchaseId != null">purchase_id = #{purchaseId},</if>
            <if test="stock != null">stock = #{stock},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
        </set>
        where warning_id = #{warningId}
    </update>

    <delete id="deletePillExpiryWarningByWarningId" parameterType="Long">
        delete from pill_expiry_warning where warning_id = #{warningId}
    </delete>

    <delete id="deletePillExpiryWarningByWarningIds" parameterType="Long[]">
        delete from pill_expiry_warning where warning_id in
        <foreach item="warningId" collection="array" open="(" separator="," close=")">
            #{warningId}
        </foreach>
    </delete>
</mapper>

3.
<?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.pill.mapper.PillFactoryMapper">

    <resultMap type="PillFactory" id="PillFactoryResult">
        <result property="factoryId"    column="factory_id"    />
        <result property="factoryName"    column="factory_name"    />
        <result property="factoryCode"    column="factory_code"    />
        <result property="contact"    column="contact"    />
        <result property="phone"    column="phone"    />
        <result property="keyword"    column="keyword"    />
        <result property="status"    column="status"    />
        <result property="createBy"    column="create_by"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
        <result property="updateTime"    column="update_time"    />
        <result property="remark"    column="remark"    />
    </resultMap>

    <sql id="selectPillFactoryVo">
        select factory_id, factory_name, factory_code, contact, phone, keyword, status, create_by, create_time, update_by, update_time, remark from pill_factory
    </sql>

    <select id="selectPillFactoryList" parameterType="PillFactory" resultMap="PillFactoryResult">
        <include refid="selectPillFactoryVo"/>
        <where>
            <if test="factoryName != null and factoryName != ''"> and factory_name like concat('%', #{factoryName}, '%')</if>
            <if test="factoryCode != null and factoryCode != ''"> and factory_code like concat('%', #{factoryCode}, '%')</if>
            <if test="contact != null and contact != ''"> and contact like concat('%', #{contact}, '%')</if>
            <if test="phone != null and phone != ''"> and phone like concat('%', #{phone}, '%')</if>
            <if test="keyword != null and keyword != ''"> and keyword like concat('%', #{keyword}, '%')</if>
            <if test="status != null and status != ''"> and status like concat('%', #{status}, '%')</if>
        </where>
    </select>

    <select id="selectPillFactoryByFactoryId" parameterType="Long" resultMap="PillFactoryResult">
        <include refid="selectPillFactoryVo"/>
        where factory_id = #{factoryId}
    </select>

    <insert id="insertPillFactory" parameterType="PillFactory" useGeneratedKeys="true" keyProperty="factoryId">
        insert into pill_factory
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="factoryName != null">factory_name,</if>
            <if test="factoryCode != null">factory_code,</if>
            <if test="contact != null">contact,</if>
            <if test="phone != null">phone,</if>
            <if test="keyword != null">keyword,</if>
            <if test="status != null">status,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="factoryName != null">#{factoryName},</if>
            <if test="factoryCode != null">#{factoryCode},</if>
            <if test="contact != null">#{contact},</if>
            <if test="phone != null">#{phone},</if>
            <if test="keyword != null">#{keyword},</if>
            <if test="status != null">#{status},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="remark != null">#{remark},</if>
         </trim>
    </insert>

    <update id="updatePillFactory" parameterType="PillFactory">
        update pill_factory
        <trim prefix="SET" suffixOverrides=",">
            <if test="factoryName != null">factory_name = #{factoryName},</if>
            <if test="factoryCode != null">factory_code = #{factoryCode},</if>
            <if test="contact != null">contact = #{contact},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="keyword != null">keyword = #{keyword},</if>
            <if test="status != null">status = #{status},</if>
            <if test="createBy != null">create_by = #{createBy},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where factory_id = #{factoryId}
    </update>

    <delete id="deletePillFactoryByFactoryId" parameterType="Long">
        delete from pill_factory where factory_id = #{factoryId}
    </delete>

    <delete id="deletePillFactoryByFactoryIds" parameterType="String">
        delete from pill_factory where factory_id in
        <foreach item="factoryId" collection="array" open="(" separator="," close=")">
            #{factoryId}
        </foreach>
    </delete>
</mapper>

4.
<?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.pill.mapper.PillPurchaseMapper">

    <resultMap type="com.ruoyi.pill.domain.PillPurchase" id="PillPurchaseResult">
        <result property="purchaseId"    column="purchase_id"/>
        <result property="drugId"    column="drug_id"/>
        <result property="factoryId"    column="factory_id"/>
        <result property="purchaseTime"    column="purchase_time"/>
        <result property="purchasePrice"    column="purchase_price"/>
        <result property="purchaseNum"    column="purchase_num"/>
        <result property="remark"    column="remark"/>
        <result property="createBy"    column="create_by"/>
        <result property="createTime"    column="create_time"/>
        <result property="updateBy"    column="update_by"/>
        <result property="updateTime"    column="update_time"/>
        <!-- 关联字段 -->
        <result property="drugName" column="drug_name"/>
        <result property="factoryName" column="factory_name"/>
    </resultMap>

    <sql id="selectPillPurchaseVo">
        SELECT
            p.purchase_id,
            p.drug_id,
            p.factory_id,
            p.purchase_time,
            p.purchase_price,
            p.purchase_num,
            p.remark,
            p.create_by,
            p.create_time,
            p.update_by,
            p.update_time,
            d.drug_name,
            f.factory_name
        FROM pill_purchase p
                 LEFT JOIN pill_drug d ON p.drug_id = d.drug_id
                 LEFT JOIN pill_factory f ON p.factory_id = f.factory_id
    </sql>

    <select id="selectPillPurchaseList" parameterType="com.ruoyi.pill.domain.PillPurchase" resultMap="PillPurchaseResult">
        <include refid="selectPillPurchaseVo"/>
        <where>
            <if test="drugId != null "> AND p.drug_id = #{drugId}</if>
            <if test="factoryId != null "> AND p.factory_id = #{factoryId}</if>
            <if test="purchaseTime != null "> AND p.purchase_time = #{purchaseTime}</if>
            <if test="purchasePrice != null "> AND p.purchase_price = #{purchasePrice}</if>
            <if test="purchaseNum != null "> AND p.purchase_num = #{purchaseNum}</if>
            <if test="drugName != null and drugName != ''"> AND d.drug_name LIKE CONCAT('%', #{drugName}, '%')</if>
            <if test="factoryName != null and factoryName != ''"> AND f.factory_name LIKE CONCAT('%', #{factoryName}, '%')</if>
        </where>
    </select>

    <select id="selectPillPurchaseByPurchaseId" parameterType="long" resultMap="PillPurchaseResult">
        <include refid="selectPillPurchaseVo"/>
        WHERE purchase_id = #{purchaseId}
    </select>

    <insert id="insertPillPurchase" parameterType="com.ruoyi.pill.domain.PillPurchase" useGeneratedKeys="true" keyProperty="purchaseId">
        INSERT INTO pill_purchase
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="drugId != null">drug_id,</if>
            <if test="factoryId != null">factory_id,</if>
            <if test="purchaseTime != null">purchase_time,</if>
            <if test="purchasePrice != null">purchase_price,</if>
            <if test="purchaseNum != null">purchase_num,</if>
            <if test="remark != null">remark,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
        </trim>
        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
            <if test="drugId != null">#{drugId},</if>
            <if test="factoryId != null">#{factoryId},</if>
            <if test="purchaseTime != null">#{purchaseTime},</if>
            <if test="purchasePrice != null">#{purchasePrice},</if>
            <if test="purchaseNum != null">#{purchaseNum},</if>
            <if test="remark != null">#{remark},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
        </trim>
    </insert>

    <update id="updatePillPurchase" parameterType="com.ruoyi.pill.domain.PillPurchase">
        UPDATE pill_purchase
        <set>
            <if test="drugId != null">drug_id = #{drugId},</if>
            <if test="factoryId != null">factory_id = #{factoryId},</if>
            <if test="purchaseTime != null">purchase_time = #{purchaseTime},</if>
            <if test="purchasePrice != null">purchase_price = #{purchasePrice},</if>
            <if test="purchaseNum != null">purchase_num = #{purchaseNum},</if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="createBy != null">create_by = #{createBy},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
        </set>
        WHERE purchase_id = #{purchaseId}
    </update>

    <!-- 查询当前库存 -->
    <select id="selectCurrentInventoryByDrugId" parameterType="long" resultType="int">
        SELECT num FROM pill_drug WHERE drug_id = #{drugId}
    </select>

    <!-- 更新库存，必带WHERE条件 -->
    <update id="updateInventoryByDrugId" parameterType="map">
        UPDATE pill_drug SET num = #{inventory} WHERE drug_id = #{drugId}
    </update>

    <delete id="deletePillPurchaseByPurchaseId" parameterType="long">
        DELETE FROM pill_purchase WHERE purchase_id = #{purchaseId}
    </delete>

    <delete id="deletePillPurchaseByPurchaseIds" parameterType="long[]">
        DELETE FROM pill_purchase WHERE purchase_id IN
        <foreach collection="array" item="purchaseId" open="(" separator="," close=")">
            #{purchaseId}
        </foreach>
    </delete>

</mapper>


前端
1.
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="药品名称" prop="drugName">
        <el-input
          v-model="queryParams.drugName"
          placeholder="请输入药品名称"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="生产厂家" >
        <el-input
          v-model="queryParams['pillFactory.factoryName']"
          placeholder="请输入生产厂家"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="药品类型" prop="drugType">
        <el-select v-model="queryParams.drugType" placeholder="请选择药品类型" clearable>
          <el-option
            v-for="dict in dict.type.pill_drug_ft"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="处方类型" prop="prescriptionType">
        <el-select v-model="queryParams.prescriptionType" placeholder="请选择处方类型" clearable>
          <el-option
            v-for="dict in dict.type.pill_drug_pt"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
          <el-option
            v-for="dict in dict.type.sys_normal_disable"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['pill:drug:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['pill:drug:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['pill:drug:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['pill:drug:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <el-table v-loading="loading" :data="drugList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="药品ID" align="center" prop="drugId" width="80"/>
      <el-table-column label="药品名称" align="center" prop="drugName" min-width="150"/>
      <el-table-column label="药品编码" align="center" prop="drugCode" />
      <el-table-column label="生产厂家" align="center" prop="pillFactory.factoryName" min-width="200"/>
      <el-table-column label="厂家编码" align="center" prop="pillFactory.factoryCode" />
      <el-table-column label="药品类型" align="center" prop="drugType">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.pill_drug_ft" :value="scope.row.drugType"/>
        </template>
      </el-table-column>
      <el-table-column label="处方类型" align="center" prop="prescriptionType">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.pill_drug_pt" :value="scope.row.prescriptionType"/>
        </template>
      </el-table-column>
      <el-table-column label="价格" align="center" prop="price" />
      <el-table-column label="库存量" align="center" prop="num" />

      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="备注" align="center" prop="remark" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['pill:drug:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['pill:drug:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改药品信息对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="药品名称" prop="drugName">
          <el-input v-model="form.drugName" placeholder="请输入药品名称" />
        </el-form-item>
        <el-form-item label="药品编码" prop="drugCode">
          <el-input v-model="form.drugCode" placeholder="请输入药品编码" />
        </el-form-item>

        <el-form-item label="生产厂家" prop="factoryId">
<!--          TODO 1.生产厂家下拉框-->
          <el-select v-model="form.factoryId" placeholder="请选择生产厂家">
            <el-option
              v-for="item in factoryList"
              :key="item.factoryId"
              :label="item.factoryName"
              :value="item.factoryId"
            ></el-option>
          </el-select>

        </el-form-item>
        <el-form-item label="药品类型" prop="drugType">
          <el-select v-model="form.drugType" placeholder="请选择药品类型">
            <el-option
              v-for="dict in dict.type.pill_drug_ft"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="处方类型" prop="prescriptionType">
          <el-select v-model="form.prescriptionType" placeholder="请选择处方类型">
            <el-option
              v-for="dict in dict.type.pill_drug_pt"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="价格" prop="price">
          <el-input v-model="form.price" placeholder="请输入价格" />
        </el-form-item>
        <el-form-item label="库存量" prop="num">
          <el-input v-model="form.num" placeholder="请输入库存量" />
        </el-form-item>

        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio
              v-for="dict in dict.type.sys_normal_disable"
              :key="dict.value"
              :label="dict.value"
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listDrug, getDrug, delDrug, addDrug, updateDrug } from "@/api/pill/drug"
import {listFactory} from "@/api/pill/factory";
export default {
  name: "Drug",
  dicts: ['pill_drug_pt', 'pill_drug_ft', 'sys_normal_disable'],
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 药品信息表格数据
      drugList: [],
      //  TODO 2.生产厂家列表
      factoryList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        drugName: null,
        factoryId: null,
        drugType: null,
        prescriptionType: null,
        status: null,
        'pillFactory.factoryName': null,
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
        drugName: [
          { required: true, message: "药品名称不能为空", trigger: "blur" }
        ],
        factoryId: [
          { required: true, message: "生产厂家不能为空", trigger: "blur" }
        ],
        drugType: [
          { required: true, message: "药品类型不能为空", trigger: "change" }
        ],
      }
    }
  },
  created() {
    this.getList()
    this.getFactoryList();
  },

  methods: {
    /** 查询药品信息列表 */
    getFactoryList(){
      listFactory().then(response => {
        this.factoryList = response.rows;
      });
    },
    /**TODO 3.查询参加信息列表 */
    getList() {
      this.loading = true
      listDrug(this.queryParams).then(response => {
        this.drugList = response.rows
        this.total = response.total
        this.loading = false
      })
    },
    // 取消按钮
    cancel() {
      this.open = false
      this.reset()
    },
    // 表单重置
    reset() {
      this.form = {
        drugId: null,
        drugName: null,
        drugCode: null,
        factoryId: null,
        drugType: null,
        prescriptionType: null,
        unit: null,
        price: null,
        num: null,
        warnValue: null,
        conversion: null,
        status: null,
        createBy: null,
        createTime: null,
        updateBy: null,
        updateTime: null,
        remark: null
      }
      this.resetForm("form")
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm")
      this.handleQuery()
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.drugId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset()
      this.open = true
      this.title = "添加药品信息"
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset()
      const drugId = row.drugId || this.ids
      getDrug(drugId).then(response => {
        this.form = response.data
        this.open = true
        this.title = "修改药品信息"
      })
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.drugId != null) {
            updateDrug(this.form).then(response => {
              this.$modal.msgSuccess("修改成功")
              this.open = false
              this.getList()
            })
          } else {
            addDrug(this.form).then(response => {
              this.$modal.msgSuccess("新增成功")
              this.open = false
              this.getList()
            })
          }
        }
      })
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const drugIds = row.drugId || this.ids
      this.$modal.confirm('是否确认删除药品信息编号为"' + drugIds + '"的数据项？').then(function() {
        return delDrug(drugIds)
      }).then(() => {
        this.getList()
        this.$modal.msgSuccess("删除成功")
      }).catch(() => {})
    },
    /** 导出按钮操作 */
    handleExport() {
      this.download('pill/drug/export', {
        ...this.queryParams
      }, `drug_${new Date().getTime()}.xlsx`)
    }
  }
}
</script>

2.
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="厂家名称" prop="factoryName">
        <el-input
          v-model="queryParams.factoryName"
          placeholder="请输入厂家名称"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>

      <el-form-item label="电话" prop="phone">
        <el-input
          v-model="queryParams.phone"
          placeholder="请输入电话"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="关键字" prop="keyword">
        <el-input
          v-model="queryParams.keyword"
          placeholder="请输入关键字"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['pill:factory:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['pill:factory:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['pill:factory:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['pill:factory:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <el-table v-loading="loading" :data="factoryList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="厂家ID" align="center" prop="factoryId" min-width="80"/>
      <el-table-column label="厂家名称" align="center" prop="factoryName" min-width="200"/>
      <el-table-column label="厂家编码" align="center" prop="factoryCode" />
      <el-table-column label="联系人" align="center" prop="contact" />
      <el-table-column label="电话" align="center" prop="phone" />
      <el-table-column label="关键字" align="center" prop="keyword" />

      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>

      <el-table-column label="创建时间" align="center" prop="createTime" min-width="150"/>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['pill:factory:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['pill:factory:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改生产厂家信息对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="厂家名称" prop="factoryName">
          <el-input v-model="form.factoryName" placeholder="请输入厂家名称" />
        </el-form-item>
        <el-form-item label="厂家编码" prop="factoryCode">
          <el-input v-model="form.factoryCode" placeholder="请输入厂家编码" />
        </el-form-item>
        <el-form-item label="联系人" prop="contact">
          <el-input v-model="form.contact" placeholder="请输入联系人" />
        </el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="form.phone" placeholder="请输入电话" />
        </el-form-item>
        <el-form-item label="关键字" prop="keyword">
          <el-input v-model="form.keyword" placeholder="请输入关键字" />
        </el-form-item>
        <el-form-item label="厂家状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio
              v-for="dict in dict.type.sys_normal_disable"
              :key="dict.value"
              :label="dict.value"
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listFactory, getFactory, delFactory, addFactory, updateFactory } from "@/api/pill/factory"

export default {
  name: "Factory",
  dicts: ['sys_normal_disable'],
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 生产厂家信息表格数据
      factoryList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        factoryName: null,
        factoryCode: null,
        contact: null,
        phone: null,
        keyword: null,
        status: null,
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
        factoryName: [
          { required: true, message: "厂家名称不能为空", trigger: "blur" }
        ],
        factoryCode: [
          { required: true, message: "厂家编码不能为空", trigger: "blur" }
        ],
        phone: [
          { required: true, message: "厂家电话不能为空", trigger: "blur" }
        ],
        status: [
          { required: true, message: "厂家状态不能为空", trigger: "blur" }
        ]
      }
    }
  },
  created() {
    this.getList()
  },
  methods: {
    /** 查询生产厂家信息列表 */
    getList() {
      this.loading = true
      listFactory(this.queryParams).then(response => {
        this.factoryList = response.rows
        this.total = response.total
        this.loading = false
      })
    },
    // 取消按钮
    cancel() {
      this.open = false
      this.reset()
    },
    // 表单重置
    reset() {
      this.form = {
        factoryId: null,
        factoryName: null,
        factoryCode: null,
        contact: null,
        phone: null,
        keyword: null,
        postSort: "0",
        status: "0",
        createBy: null,
        createTime: null,
        updateBy: null,
        updateTime: null,
        remark: undefined
      }
      this.resetForm("form")
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm")
      this.handleQuery()
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.factoryId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset()
      this.open = true
      this.title = "添加生产厂家信息"
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset()
      const factoryId = row.factoryId || this.ids
      getFactory(factoryId).then(response => {
        this.form = response.data
        this.open = true
        this.title = "修改生产厂家信息"
      })
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.factoryId != null) {
            updateFactory(this.form).then(response => {
              this.$modal.msgSuccess("修改成功")
              this.open = false
              this.getList()
            })
          } else {
            addFactory(this.form).then(response => {
              this.$modal.msgSuccess("新增成功")
              this.open = false
              this.getList()
            })
          }
        }
      })
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const factoryIds = row.factoryId || this.ids
      this.$modal.confirm('是否确认删除生产厂家信息编号为"' + factoryIds + '"的数据项？').then(function() {
        return delFactory(factoryIds)
      }).then(() => {
        this.getList()
        this.$modal.msgSuccess("删除成功")
      }).catch(() => {})
    },
    /** 导出按钮操作 */
    handleExport() {
      this.download('pill/factory/export', {
        ...this.queryParams
      }, `factory_${new Date().getTime()}.xlsx`)
    }
  }
}
</script>

3.
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="药品名称" prop="drugId">
        <el-select v-model="queryParams.drugId" placeholder="请选择药品" clearable>
          <el-option v-for="item in drugList" :key="item.drugId" :label="item.drugName" :value="item.drugId" />
        </el-select>
      </el-form-item>
      <el-form-item label="生产厂家" prop="factoryId">
        <el-select v-model="queryParams.factoryId" placeholder="请选择生产厂家" clearable>
          <el-option v-for="item in factoryList" :key="item.factoryId" :label="item.factoryName" :value="item.factoryId" />
        </el-select>
      </el-form-item>
      <el-form-item label="进货时间" prop="purchaseTime">
        <el-date-picker clearable
                        v-model="queryParams.purchaseTime"
                        type="date"
                        value-format="yyyy-MM-dd"
                        placeholder="请选择进货时间">
        </el-date-picker>
      </el-form-item>
      <el-form-item label="进货单价" prop="purchasePrice">
        <el-input
          v-model="queryParams.purchasePrice"
          placeholder="请输入进货单价"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['pill:purchase:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['pill:purchase:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['pill:purchase:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['pill:purchase:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <el-table v-loading="loading" :data="purchaseList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="进货单ID" align="center" prop="purchaseId" />
      <el-table-column label="药品名称" align="center" prop="drugName" />
      <el-table-column label="生产厂家" align="center" prop="factoryName" />
      <el-table-column label="进货时间" align="center" prop="purchaseTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.purchaseTime, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="进货单价" align="center" prop="purchasePrice" />
      <el-table-column label="进货数量" align="center" prop="purchaseNum" />
      <el-table-column label="进货备注" align="center" prop="remark" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['pill:purchase:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['pill:purchase:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改药品进货记录对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="药品名称" prop="drugId">
          <el-select v-model="form.drugId" placeholder="请选择药品" clearable>
            <el-option v-for="item in drugList" :key="item.drugId" :label="item.drugName" :value="item.drugId" />
          </el-select>
        </el-form-item>
        <el-form-item label="生产厂家" prop="factoryId">
          <el-select v-model="form.factoryId" placeholder="请选择生产厂家" clearable>
            <el-option v-for="item in factoryList" :key="item.factoryId" :label="item.factoryName" :value="item.factoryId" />
          </el-select>
        </el-form-item>
        <el-form-item label="进货时间" prop="purchaseTime">
          <el-date-picker clearable
                          v-model="form.purchaseTime"
                          type="date"
                          value-format="yyyy-MM-dd"
                          placeholder="请选择进货时间">
          </el-date-picker>
        </el-form-item>
        <el-form-item label="进货单价" prop="purchasePrice">
          <el-input v-model="form.purchasePrice" placeholder="请输入进货单价" />
        </el-form-item>
        <el-form-item label="进货数量" prop="purchaseNum">
          <el-input v-model="form.purchaseNum" placeholder="请输入进货数量" />
        </el-form-item>
        <el-form-item label="进货备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listPurchase, getPurchase, delPurchase, addPurchase, updatePurchase } from "@/api/pill/purchase"
import { listDrug } from "@/api/pill/drug" // 新增药品API
import { listFactory } from "@/api/pill/factory" // 新增厂家API

export default {
  name: "Purchase",
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 药品进货记录表格数据
      purchaseList: [],
      // 药品列表
      drugList: [],
      // 厂家列表
      factoryList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        drugId: null,
        factoryId: null,
        purchaseTime: null,
        purchasePrice: null,
        purchaseNum: null,
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
        drugId: [
          { required: true, message: "请选择药品", trigger: "change" }
        ],
        factoryId: [
          { required: true, message: "请选择生产厂家", trigger: "change" }
        ],
        purchaseTime: [
          { required: true, message: "请选择进货时间", trigger: "blur" }
        ],
        purchasePrice: [
         { required: true, message: "请输入进货单价", trigger: "blur" },
         { pattern: /^\d+(\.\d+)?$/, message: '请输入有效的数字值', trigger: 'blur' }
       ],
       purchaseNum: [
         { required: true, message: "请输入进货数量", trigger: "blur" },
         { pattern: /^\d+$/, message: '请输入有效的数字值', trigger: 'blur' }
       ]
       ,
      }
    }
  },
  created() {
    this.getList()
    this.getDrugList()
    this.getFactoryList()
  },
  methods: {

    /** 查询药品进货记录列表 */
    getList() {
      this.loading = true
      listPurchase(this.queryParams).then(response => {
        this.purchaseList = response.rows
        this.total = response.total
        this.loading = false
      })
    },

    /** 获取药品列表 */
    getDrugList() {
      listDrug().then(response => {
        this.drugList = response.rows
      })
    },

    /** 获取厂家列表 */
    getFactoryList() {
      listFactory().then(response => {
        this.factoryList = response.rows
      })
    },

    // 取消按钮
    cancel() {
      this.open = false
      this.reset()
    },
    // 表单重置
    reset() {
      this.form = {
        purchaseId: null,
        drugId: null,
        factoryId: null,
        purchaseTime: null,
        purchasePrice: null,
        purchaseNum: null,
        remark: null,
      }
      this.resetForm("form")
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm")
      this.handleQuery()
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.purchaseId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset()
      this.open = true
      this.title = "添加药品进货记录"
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset()
      const purchaseId = row.purchaseId || this.ids
      getPurchase(purchaseId).then(response => {
        this.form = response.data
        this.open = true
        this.title = "修改药品进货记录"
      })
    },
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.purchaseId != null) {
            updatePurchase(this.form).then(() => {
              this.$modal.msgSuccess("修改成功");
              this.open = false;
              this.getList();
              // 不要调用updateDrugStock，因为后端库存会自动更新
            });
          } else {
            addPurchase(this.form).then(() => {
              this.$modal.msgSuccess("新增成功");
              this.open = false;
              this.getList();
              // 不要调用updateDrugStock，因为后端库存会自动更新
            });
          }
        }
      });
    },
    // 更新药品库存
    updateDrugStock(drugId, purchaseNum) {
      // 调用获取药品信息的接口
      getDrug(drugId).then(response => {
        const drug = response.data;
        // 确保 drug.num 是数字类型
        drug.num = Number(drug.num) + Number(purchaseNum);
        // 调用更新药品信息的接口
        updateDrug(drug).then(() => {
          this.$modal.msgSuccess("库存更新成功");
        });
      });
    },




    /** 删除按钮操作 */
    handleDelete(row) {
      const purchaseIds = row.purchaseId || this.ids
      this.$modal.confirm('是否确认删除药品进货记录？').then(function() {
        return delPurchase(purchaseIds)
      }).then(() => {
        this.getList()
        this.$modal.msgSuccess("删除成功")
      }).catch(() => {})
    },
    /** 导出按钮操作 */
    handleExport() {
      this.download('pill/purchase/export', {
        ...this.queryParams
      }, `purchase_${new Date().getTime()}.xlsx`)
    }
  }
}
</script>

4.
<template>
  <div class="app-container">
    <!-- 搜索表单 -->
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="药品ID" prop="drugId">
        <el-input v-model="queryParams.drugId" placeholder="请输入药品ID" clearable @keyup.enter.native="handleQuery" />
      </el-form-item>
      <el-form-item label="厂家ID" prop="factoryId">
        <el-input v-model="queryParams.factoryId" placeholder="请输入厂家ID" clearable @keyup.enter.native="handleQuery" />
      </el-form-item>
      <el-form-item label="生产日期" prop="productionDate">
        <el-date-picker clearable v-model="queryParams.productionDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择生产日期"/>
      </el-form-item>
      <el-form-item label="保质期" prop="validityPeriod">
        <el-input v-model="queryParams.validityPeriod" placeholder="请输入保质期" clearable @keyup.enter.native="handleQuery" />
      </el-form-item>
      <el-form-item label="进货单ID" prop="purchaseId">
        <el-input v-model="queryParams.purchaseId" placeholder="请输入进货单ID" clearable @keyup.enter.native="handleQuery" />
      </el-form-item>

      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
        <el-button type="warning" size="mini" :plain="!queryParams.onlyExpire" @click="toggleOnlyExpire">
          {{ queryParams.onlyExpire ? '查看全部' : '查看过期' }}
        </el-button>
      </el-form-item>
    </el-form>

    <!-- 操作按钮 -->
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['pill:warning:add']">新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['pill:warning:edit']">修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['pill:warning:remove']">删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['pill:warning:export']">导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <!-- 列表 -->
    <el-table v-loading="loading" :data="warningList" @selection-change="handleSelectionChange" stripe border>
      <el-table-column type="selection" width="55" align="center"/>
      <el-table-column label="主键ID" align="center" prop="warningId"/>
      <el-table-column label="药品ID" align="center" prop="drugId"/>
      <el-table-column label="药品名称" align="center" prop="drugName"/>
      <el-table-column label="厂家ID" align="center" prop="factoryId"/>
      <el-table-column label="厂家名称" align="center" prop="factoryName"/>
      <el-table-column label="生产日期" align="center" prop="productionDate" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}')}}</span>
        </template>
      </el-table-column>
      <el-table-column label="保质期" align="center" prop="validityPeriod"/>
      <el-table-column label="进货单ID" align="center" prop="purchaseId"/>
      <el-table-column label="库存数量" align="center" prop="stock"/>
      <el-table-column label="剩余天数" align="center" prop="daysToExpire" width="110">
        <template slot-scope="scope">
          <span :style="{ color: scope.row.daysToExpire <= 15 ? 'red' : 'inherit', fontWeight: scope.row.daysToExpire <= 15 ? 'bold' : 'normal' }">
            {{ scope.row.daysToExpire }}
          </span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['pill:warning:edit']">修改</el-button>
          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['pill:warning:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />

    <!-- 弹窗 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="药品ID" prop="drugId">
          <el-input v-model="form.drugId" placeholder="请输入药品ID"/>
        </el-form-item>
        <el-form-item label="厂家ID" prop="factoryId">
          <el-input v-model="form.factoryId" placeholder="请输入厂家ID"/>
        </el-form-item>
        <el-form-item label="生产日期" prop="productionDate">
          <el-date-picker clearable v-model="form.productionDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择生产日期"/>

        </el-form-item>
        <el-form-item label="保质期" prop="validityPeriod">
          <el-input v-model="form.validityPeriod" placeholder="请输入保质期"/>
        </el-form-item>
        <el-form-item label="进货单ID" prop="purchaseId">
          <el-input v-model="form.purchaseId" placeholder="请输入进货单ID"/>
        </el-form-item>
        <!-- 不显示库存输入 -->
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listWarning, getWarning, delWarning, addWarning, updateWarning } from "@/api/pill/warning"

export default {
  name: "Warning",
  data() {
    return {
      loading: true,
      ids: [],
      single: true,
      multiple: true,
      showSearch: true,
      total: 0,
      warningList: [],
      title: "",
      open: false,
      // 新增onlyExpire字段控制是否只看过期15天内的数据
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        drugId: null,
        factoryId: null,
        productionDate: null,
        validityPeriod: null,
        purchaseId: null,
        onlyExpire: false, // 新增标识
      },
      form: {},
      rules: {
        drugId: [{ required: true, message: "药品ID不能为空", trigger: "blur" }],
        factoryId: [{ required: true, message: "厂家ID不能为空", trigger: "blur" }],
        productionDate: [{ required: true, message: "生产日期不能为空", trigger: "blur" }],
        validityPeriod: [{ required: true, message: "保质期不能为空", trigger: "blur" }],
        purchaseId: [{ required: true, message: "进货单ID不能为空", trigger: "blur" }],
      }
    }
  },
  created() {
    this.getList()
  },
  methods: {
    // 点击“查看过期”按钮，切换筛选状态
    toggleOnlyExpire() {
      this.queryParams.onlyExpire = !this.queryParams.onlyExpire
      this.queryParams.pageNum = 1
      this.getList()
    },
    getList() {
      this.loading = true
      // 这里调用接口时把onlyExpire传递给后端，后端根据该字段筛选daysToExpire <= 15
      listWarning(this.queryParams).then(response => {
        this.warningList = response.rows
        this.total = response.total
        this.loading = false
      })
    },
    cancel() {
      this.open = false
      this.reset()
    },
    reset() {
      this.form = {
        warningId: null,
        drugId: null,
        factoryId: null,
        productionDate: null,
        validityPeriod: null,
        purchaseId: null,
        createTime: null,
        stock: null,
        daysToExpire: null
      }
      this.resetForm("form")
    },
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
    },
    resetQuery() {
      this.resetForm("queryForm")
      this.queryParams.onlyExpire = false // 重置时恢复全部数据
      this.handleQuery()
    },
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.warningId)
      this.single = selection.length !== 1
      this.multiple = !selection.length
    },
    handleAdd() {
      this.reset()
      this.open = true
      this.title = "添加药品有效期预警，用于监控药品保质期"
      // 新增时，set stock = 0 避免数据库报错
      this.form.stock = 0
    },
    handleUpdate(row) {
      this.reset()
      const warningId = row.warningId || this.ids
      getWarning(warningId).then(response => {
        this.form = response.data
        this.open = true
        this.title = "修改药品有效期预警，用于监控药品保质期"
        if (this.form.stock == null) {
          this.form.stock = 0
        }
      })
    },
    submitForm() {
      this.$refs.form.validate(valid => {
        if (valid) {
          if (this.form.stock == null) {
            this.form.stock = 0
          }
          if (this.form.warningId != null) {
            updateWarning(this.form).then(() => {
              this.$modal.msgSuccess("修改成功")
              this.open = false
              this.getList()
            })
          } else {
            addWarning(this.form).then(() => {
              this.$modal.msgSuccess("新增成功")
              this.open = false
              this.getList()
            })
          }
        }
      })
    },
    handleDelete(row) {
      const warningIds = row.warningId || this.ids
      this.$modal.confirm(`是否确认删除药品有效期预警，用于监控药品保质期编号为"${warningIds}"的数据项？`).then(() => {
        return delWarning(warningIds)
      }).then(() => {
        this.getList()
        this.$modal.msgSuccess("删除成功")
      }).catch(() => {})
    },
    handleExport() {
      // 导出时只传onlyExpire参数，告诉后端导出筛选后的数据
      this.download('pill/warning/export', { ...this.queryParams }, `warning_${Date.now()}.xlsx`)
    }
  }
}
</script>



登录前端代码：
该项目的登录和注册核心代码主要集中在以下几个部分：

### 登录核心代码
1. **`login.vue`** 文件中定义了登录页面的结构和逻辑，包括：
   - 用户名、密码输入框。
   - 验证码功能。
   - 记住密码功能。
   - 登录按钮事件处理 (`handleLogin`)。
   - 获取验证码 (`getCode`) 和读取 Cookie (`getCookie`) 方法。

   核心逻辑片段：
   - `handleLogin`：验证表单并调用 Vuex 的 `Login` action。
   - [getCodeImg](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\api\login.js#L50-L59)：获取验证码图片。
   - 使用 `Cookies` 存储用户名和密码（记住密码功能）。
   - 调用 `store.dispatch("Login", this.loginForm)` 实现登录操作。

2. **`src/api/login.js`** 文件中定义了登录请求的方法：
   - [login(username, password, code, uuid)](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\api\login.js#L3-L19)：向 `/login` 接口发送 POST 请求进行用户登录。
   - 通过 `request` 模块封装 HTTP 请求。

3. **`user.js` (Vuex Module)** 中定义了登录状态管理：
   - `actions.Login`：负责调用登录 API，并将返回的 token 提交到 `SET_TOKEN` mutation。
   - `mutations.SET_TOKEN`：保存用户的 token 到 state。
   - `GetInfo`：获取用户信息，设置用户角色权限等数据。

4. **`permission.js`** 定义了路由守卫逻辑：
   - 在 `router.beforeEach` 中检查是否登录 ([getToken()](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\utils\auth.js#L4-L6))。
   - 如果没有 token，重定向到 `/login` 页面。
   - 已登录时，根据用户角色动态加载路由。

---

### 注册核心代码
1. **`register.vue`** 文件中定义了注册页面的结构和逻辑，包括：
   - 用户名、密码、确认密码、验证码输入框。
   - 表单验证规则 (`registerRules`)。
   - 注册按钮事件处理 (`handleRegister`)。
   - 获取验证码 (`getCode`) 方法。

   核心逻辑片段：
   - `handleRegister`：验证表单后调用 [register](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\api\login.js#L22-L31) API 提交注册信息。
   - 显示注册成功提示，并跳转至登录页。

2. **`src/api/login.js`** 文件中定义了注册请求的方法：
   - [register(data)](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\api\login.js#L22-L31)：向 `/register` 接口发送 POST 请求进行用户注册。

---

### 总结
- **登录核心文件**：
  - `login.vue`：前端登录页面交互逻辑。
  - `api/login.js`：登录接口定义。
  - `store/modules/user.js`：登录状态管理和用户信息获取。
  - `permission.js`：路由守卫控制访问权限。

- **注册核心文件**：
  - `register.vue`：前端注册页面交互逻辑。
  - `api/login.js`：注册接口定义。


登录后端代码：
理解你的问题: doing
收集相关的信息: doing
该项目的登录注册核心代码主要分布在以下几个关键类和文件中：

### 登录核心代码
1. **`Login.vue`**  
   - 路径：`ruoyi-ui/src/views/login.vue`
   - 作用：
     - 前端登录页面模板，包含用户名、密码输入框及验证码功能。
     - 处理用户登录逻辑，调用 `handleLogin()` 方法进行表单验证并提交登录请求。
     - 使用 `store.dispatch("Login", this.loginForm)` 提交登录信息到 Vuex Store。

2. **`LoginBody.java`**  
   - 路径：`ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java`
   - 作用：
     - 定义登录请求的数据结构，包括用户名、密码、验证码等字段。

3. **`SysLoginController.java`**
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/controller/SysLoginController.java`
   - 作用：
     - 提供登录接口 `/login`，接收前端传入的登录请求数据（[LoginBody](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L7-L68)）。
     - 调用 `loginService.login()` 进行身份认证。
     - 返回登录成功后的 Token 及用户信息。

4. **`UserDetailsServiceImpl.java`**
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java`
   - 作用：
     - Spring Security 的核心实现类，用于从数据库加载用户信息（[SysUser](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\entity\SysUser.java#L19-L323)），构建 [LoginUser](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginUser.java#L14-L265) 对象。
     - 验证用户账号是否存在以及是否被禁用。

5. **`JwtTokenUtil.java`**
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/util/JwtTokenUtil.java`
   - 作用：
     - 生成 JWT Token，并设置过期时间。
     - 提供解析 Token 和校验 Token 是否有效的工具方法。

6. **`SecurityConfig.java`**
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/config/SecurityConfig.java`
   - 作用：
     - 配置 Spring Security 的安全策略，如放行静态资源、登录接口等。
     - 启用 JWT 过滤器，拦截请求并验证 Token。

---

### 注册核心代码
1. **`Register.vue`**  
   - 路径：`ruoyi-ui/src/views/register.vue`
   - 作用：
     - 前端注册页面模板，包含用户名、密码、确认密码、验证码等输入项。
     - 处理用户注册逻辑，调用 `handleRegister()` 方法进行表单验证并提交注册请求。

2. **`RegisterBody.java`**  
   - 路径：`ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java`
   - 作用：
     - 继承自 [LoginBody](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L7-L68)，扩展了验证码字段，用于注册时的数据传输。

3. **`SysRegisterService.java`**  
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java`
   - 作用：
     - 校验注册参数（用户名长度、密码强度等）。
     - 检查用户名是否唯一。
     - 将用户密码加密后存入数据库。
     - 记录注册日志。

4. **`ISysUserService.java`**  
   - 路径：`ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java`
   - 作用：
     - 提供注册用户的数据库操作接口。
     - 包括插入新用户、检查用户名唯一性等方法。

5. **`SysUser.java`**  
   - 路径：`ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java`
   - 作用：
     - 用户实体类，定义用户的基本信息（用户名、密码、昵称等）。

---

### 其他相关配置
1. **`application.yml`**
   - 路径：`ruoyi-admin/src/main/resources/application.yml`
   - 作用：
     - 配置数据库连接、JWT 密钥、验证码开关等系统参数。

2. **`pom.xml`**
   - 路径：`ruoyi-framework/pom.xml`
   - 作用：
     - 引入 Spring Boot Web、Spring Security、Druid、Kaptcha 等依赖，支撑登录注册功能。

---

### 总结
该项目的登录注册核心代码集中在以下几个模块：
- **前端视图层**：`Login.vue` 和 `Register.vue`
- **数据传输对象**：`LoginBody.java` 和 `RegisterBody.java`
- **业务逻辑层**：`SysLoginController.java` 和 `SysRegisterService.java`
- **安全框架集成**：`UserDetailsServiceImpl.java`、`JwtTokenUtil.java` 和 `SecurityConfig.java`

这些模块共同构成了系统的登录注册流程。


验证码实现
理解你的问题: doing
收集相关的信息: doing
该项目的验证码核心代码主要分布在以下几个关键类和文件中：

### 验证码核心代码
1. **`CaptchaController.java`**  
   - 路径：`ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java`
   - 作用：
     - 提供生成验证码的接口 `/captchaImage`。
     - 根据配置生成数学公式或字符类型的验证码图片。
     - 将验证码信息存储在 Redis 中，并设置过期时间。

2. **`CaptchaConfig.java`**  
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java`
   - 作用：
     - 配置验证码生成器（`DefaultKaptcha`）的各种属性，如边框、颜色、字体大小等。
     - 定义两个 `Producer` Bean：[captchaProducer](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CaptchaController.java#L30-L31) 和 [captchaProducerMath](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CaptchaController.java#L33-L34)，分别用于生成普通字符验证码和数学公式验证码。

3. **`KaptchaTextCreator.java`**  
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java`
   - 作用：
     - 自定义验证码文本生成逻辑，特别是针对数学公式验证码的生成方式。

4. **`Login.vue` 和 `Register.vue`**  
   - 路径：
     - `ruoyi-ui/src/views/login.vue`
     - `ruoyi-ui/src/views/register.vue`
   - 作用：
     - 前端页面展示验证码图片，并提供点击刷新功能。
     - 在登录和注册时提交验证码进行校验。

5. **`SysRegisterService.java`**  
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java`
   - 作用：
     - 注册流程中调用 [validateCaptcha()](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-framework\src\main\java\com\ruoyi\framework\web\service\SysRegisterService.java#L100-L113) 方法验证用户输入的验证码是否正确。

6. **`SecurityConfig.java`**  
   - 路径：`ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java`
   - 作用：
     - Spring Security 配置中放行 `/captchaImage` 接口，允许匿名访问以获取验证码。

---

### 验证码实现流程

#### 1. **前端请求验证码**
- 用户在登录页（[Login.vue](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\views\login.vue#L0-L108)）或注册页（[Register.vue](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\views\register.vue#L0-L96)）点击验证码图片或初始化页面时，触发 [getCode()](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CaptchaController.java#L44-L92) 方法。
- 前端通过 [getCodeImg()](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-ui\src\api\login.js#L50-L59) 请求后端接口 `/captchaImage` 获取验证码。

#### 2. **后端生成验证码**
- 后端由 [CaptchaController.java](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CaptchaController.java#L0-L93) 处理请求。
- 从 [RuoYiConfig.getCaptchaType()](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\config\RuoYiConfig.java#L82-L84) 获取验证码类型（字符型 `"char"` 或数学公式型 `"math"`）。
- 使用对应的 `Producer`（[captchaProducer](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-framework\src\main\java\com\ruoyi\framework\config\CaptchaConfig.java#L0-L67) 或 [captchaProducerMath](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-framework\src\main\java\com\ruoyi\framework\config\CaptchaConfig.java#L64-L82)）生成验证码字符串和图片。
- 将验证码内容存入 Redis，键为 `verifyKey = CAPTCHA_CODE_KEY + uuid`，并设置过期时间（默认5分钟）。

#### 3. **返回验证码给前端**
- 后端将验证码图片转换为 Base64 编码，与唯一标识 [uuid](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L27-L27) 一起返回给前端。
- 前端接收到响应后，更新验证码图片显示，并保存 [uuid](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L27-L27)，以便后续提交验证码时使用。

#### 4. **用户提交验证码**
- 用户在登录或注册时填写验证码，前端将 [code](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L22-L22) 和 [uuid](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L27-L27) 作为参数发送给后端接口（如 `/login` 或 `/register`）。
- 后端解析 [uuid](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\core\domain\model\LoginBody.java#L27-L27)，拼接出完整的 Redis Key（`CAPTCHA_CODE_KEY + uuid`），然后从 Redis 中获取正确的验证码。
- 如果验证码不存在或不匹配，则抛出异常（[CaptchaExpireException](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\exception\user\CaptchaExpireException.java#L7-L15) 或 [CaptchaException](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-common\src\main\java\com\ruoyi\common\exception\user\CaptchaException.java#L7-L15)），提示用户验证码错误。

#### 5. **清除验证码**
- 每次成功验证或失败后，系统都会删除 Redis 中的验证码缓存，防止重复使用。

---

### 总结
验证码的实现流程如下：
1. **前端请求验证码 →**
2. **后端生成验证码并存储到 Redis →**
3. **返回验证码图片给前端 →**
4. **用户输入验证码并提交 →**
5. **后端验证是否一致 →**
6. **清除验证码缓存**

整个流程依赖于以下组件：
- **[CaptchaController](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CaptchaController.java#L27-L93)**：处理验证码生成请求。
- **[CaptchaConfig](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-framework\src\main\java\com\ruoyi\framework\config\CaptchaConfig.java#L14-L82)**：配置验证码样式。
- **[KaptchaTextCreator](file://D:\bishe111111111\RuoYi-Vue-master\ruoyi-framework\src\main\java\com\ruoyi\framework\config\KaptchaTextCreator.java#L10-L67)**：自定义验证码生成逻辑。
- **RedisCache**：临时存储验证码数据。
- **前端 Vue 组件**：展示和刷新验证码。

这种方式确保了验证码的安全性、时效性和防刷机制。




数据库sql语句
/*
Navicat MySQL Data Transfer

Source Server         : test
Source Server Version : 50717
Source Host           : localhost:3306
Source Database       : storage1

Target Server Type    : MYSQL
Target Server Version : 50717
File Encoding         : 65001

Date: 2022-09-25 17:33:23
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for pill_drug
-- ----------------------------
DROP TABLE IF EXISTS `pill_drug`;
CREATE TABLE `pill_drug` (
  `drug_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '药品ID',
  `drug_name` varchar(50) DEFAULT NULL COMMENT '药品名称',
  `drug_code` varchar(50) DEFAULT NULL COMMENT '药品编码',
  `factory_id` bigint(20) DEFAULT NULL COMMENT '生产厂家',
  `drug_type` char(1) DEFAULT NULL COMMENT '药品类型',
  `prescription_type` char(1) DEFAULT NULL COMMENT '处方类型',
  `unit` varchar(10) DEFAULT NULL COMMENT '单位',
  `price` int(10) DEFAULT NULL COMMENT '价格',
  `num` int(10) DEFAULT NULL COMMENT '库存量',
  `warn_value` int(10) DEFAULT NULL COMMENT '预警值',
  `conversion` int(10) DEFAULT NULL COMMENT '换算量',
  `status` char(1) DEFAULT NULL COMMENT '状态',
  `create_by` varchar(50) DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(50) DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`drug_id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8 COMMENT='药品信息';

-- ----------------------------
-- Records of pill_drug
-- ----------------------------
INSERT INTO `pill_drug` VALUES ('101', '十全大补丸', 'dbw0001', '101', '0', '0', '瓶', '200', '100', '50', '1', '0', 'admin', '2022-08-27 16:03:29', null, null, null);
INSERT INTO `pill_drug` VALUES ('102', '阿莫西林', 'zcy0002', '102', '1', '1', '盒', '15', '300', '50', '1', '0', 'admin', '2022-09-15 16:03:29', null, null, null);


/*
Navicat MySQL Data Transfer

Source Server         : test
Source Server Version : 50717
Source Host           : localhost:3306
Source Database       : storage

Target Server Type    : MYSQL
Target Server Version : 50717
File Encoding         : 65001

Date: 2022-09-25 15:48:01
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for pill_factory
-- ----------------------------
DROP TABLE IF EXISTS `pill_factory`;
CREATE TABLE `pill_factory` (
  `factory_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '厂家ID',
  `factory_name` varchar(50) DEFAULT NULL COMMENT '厂家名称',
  `factory_code` varchar(50) DEFAULT NULL COMMENT '厂家编码',
  `contact` varchar(30) DEFAULT NULL COMMENT '联系人',
  `phone` varchar(30) DEFAULT NULL COMMENT '电话',
  `keyword` varchar(20) DEFAULT NULL COMMENT '关键字',
  `status` char(1) DEFAULT NULL COMMENT '状态',
  `create_by` varchar(50) DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(50) DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`factory_id`)
) ENGINE=InnoDB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8 COMMENT='生产厂家信息';

-- ----------------------------
-- Records of pill_factory
-- ----------------------------
INSERT INTO `pill_factory` VALUES ('101', '云南白药集团股份有限公司', '000011', 'admin', '023-88887777', 'ynby', '0', 'admin', '2022-09-26 16:15:29', null, null, null);
INSERT INTO `pill_factory` VALUES ('102', '哈药集团股份有限公司', '000022', 'admin', '023-88887777', 'hyjt', '0', 'admin', '2022-09-26 16:15:29', 'admin', '2022-09-26 20:34:01', null);
INSERT INTO `pill_factory` VALUES ('103', '太极集团有限公司', '000033', 'admin', '023-88887777', 'tjjt', '0', 'admin', '2022-09-26 16:15:29', null, null, null);
INSERT INTO `pill_factory` VALUES ('104', '上海医药集团有限公司', '000033', 'admin', '023-99998888', 'shyy', '0', 'admin', '2022-09-26 20:12:57', null, null, null);


-- ----------------------------
-- Table structure for pill_purchase
-- ----------------------------
DROP TABLE IF EXISTS `pill_purchase`;
CREATE TABLE `pill_purchase` (
  `purchase_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '进货单ID',
  `drug_id` bigint(20) NOT NULL COMMENT '药品ID（关联pill_drug.drug_id）',
  `factory_id` bigint(20) NOT NULL COMMENT '生产厂家ID（关联pill_factory.factory_id）',
  `purchase_time` datetime NOT NULL COMMENT '进货时间',
  `purchase_price` decimal(10,2) NOT NULL COMMENT '进货单价（元）',
  `purchase_num` int(10) NOT NULL COMMENT '进货数量',
  `remark` varchar(500) DEFAULT NULL COMMENT '进货备注',
  `create_by` varchar(50) DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(50) DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`purchase_id`),
  KEY `idx_drug_id` (`drug_id`),
  KEY `idx_factory_id` (`factory_id`),
  CONSTRAINT `fk_purchase_drug` FOREIGN KEY (`drug_id`) REFERENCES `pill_drug` (`drug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `fk_purchase_factory` FOREIGN KEY (`factory_id`) REFERENCES `pill_factory` (`factory_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8 COMMENT='药品进货记录表';


DROP TABLE IF EXISTS `pill_expiry_warning`;
CREATE TABLE `pill_expiry_warning` (
  `warning_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `drug_id` bigint(20) NOT NULL COMMENT '药品ID（关联pill_drug.drug_id）',
  `factory_id` bigint(20) NOT NULL COMMENT '厂家ID（关联pill_factory.factory_id）',
  `production_date` date NOT NULL COMMENT '药品生产日期',
  `validity_period` int(10) NOT NULL COMMENT '保质期（天）',
  `purchase_id` bigint(20) NOT NULL COMMENT '进货单ID（关联pill_purchase.purchase_id）',
  `stock` int(10) NOT NULL COMMENT '库存数量',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
  PRIMARY KEY (`warning_id`),
  KEY `idx_drug_id` (`drug_id`),
  KEY `idx_factory_id` (`factory_id`),
  CONSTRAINT `fk_expiry_drug` FOREIGN KEY (`drug_id`) REFERENCES `pill_drug` (`drug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `fk_expiry_factory` FOREIGN KEY (`factory_id`) REFERENCES `pill_factory` (`factory_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `fk_expiry_purchase` FOREIGN KEY (`purchase_id`) REFERENCES `pill_purchase` (`purchase_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='药品有效期预警表，用于监控药品保质期';
描述一下药品信息管理，生产厂家管理，药品进货记录管理，效期预警管理的业务流程