文章目录
在ERP系统中,单据编号(如: RK202601190001 )的自动生成是常见的需求,格式为: [RK][日期][序号] ,其中:
- RK:代表入库单据的标识(如流水号前缀)
- 20260119:代表单据的生成日期
- 0001:标识当天生成的第一张单据

实现步骤一:创建表
创建一个 SC_BILL_SEQ_LOG 表用于存储单据编号,如下:

建表 sql 语句如下:
sql
-- Create table
create table C##RY_NET.SC_BILL_SEQ_LOG
(
id NUMBER(20) generated always as identity,
billtype VARCHAR2(50),
datekey DATE,
currentnumber NUMBER default 1
)
tablespace USERS
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
-- Add comments to the table
comment on table C##RY_NET.SC_BILL_SEQ_LOG
is '单据序列日志表';
-- Add comments to the columns
comment on column C##RY_NET.SC_BILL_SEQ_LOG.id
is '自增ID';
comment on column C##RY_NET.SC_BILL_SEQ_LOG.billtype
is '单据类型';
comment on column C##RY_NET.SC_BILL_SEQ_LOG.datekey
is '日期';
comment on column C##RY_NET.SC_BILL_SEQ_LOG.currentnumber
is '当前序号';
-- Create/Recreate primary, unique and foreign key constraints
alter table C##RY_NET.SC_BILL_SEQ_LOG
add constraint SC_BILL_SEQ_LOG_ID primary key (ID)
using index
tablespace USERS
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
实现步骤二:实体类和Dto
实体类如下:
csharp
using RuoYi.Data.Entities;
using SqlSugar;
namespace RuoYi.StockControll.Data.Entities
{
/// <summary>
/// 单据序列日志 对象 SC_BILL_SEQ_LOG
/// author cgs
/// date 2026-01-19 09:36:58
/// </summary>
[SugarTable("C##RY_NET.SC_BILL_SEQ_LOG", "单据序列日志表")]
public class ScBillSeqLog : BaseEntity
{
/// <summary>
/// 自增ID (ID)
/// </summary>
[SugarColumn(ColumnName = "ID", ColumnDescription = "自增ID", IsPrimaryKey = true, IsIdentity = true)]
public int? Id { get; set; }
/// <summary>
/// 单据类型 (BILLTYPE)
/// </summary>
[SugarColumn(ColumnName = "BILLTYPE", ColumnDescription = "单据类型")]
public string? Billtype { get; set; }
/// <summary>
/// 日期 (DATEKEY)
/// </summary>
[SugarColumn(ColumnName = "DATEKEY", ColumnDescription = "日期")]
public DateTime? Datekey { get; set; }
/// <summary>
/// 当前序号 (CURRENTNUMBER)
/// </summary>
[SugarColumn(ColumnName = "CURRENTNUMBER", ColumnDescription = "当前序号")]
public int? Currentnumber { get; set; }
}
}
DTO 如下:
csharp
using RuoYi.Data.Dtos;
namespace RuoYi.StockControll.Data.Dtos
{
/// <summary>
/// 单据序列日志 对象 SC_BILL_SEQ_LOG
/// author cgs
/// date 2026-01-19 09:36:58
/// </summary>
public class ScBillSeqLogDto : BaseDto
{
/// <summary>
/// 自增ID
/// </summary>
public int? Id { get; set; }
/// <summary>
/// 单据类型
/// </summary>
public string? Billtype { get; set; }
/// <summary>
/// 日期
/// </summary>
public DateTime? Datekey { get; set; }
/// <summary>
/// 当前序号
/// </summary>
public int? Currentnumber { get; set; }
}
}
实现步骤三:仓储层
仓储层如下:
csharp
using RuoYi.Common.Data;
using RuoYi.StockControll.Data.Dtos;
using RuoYi.StockControll.Data.Entities;
using SqlSugar;
namespace RuoYi.StockControll.Repositories
{
/// <summary>
/// 单据序列日志 Repository
/// author cgs
/// date 2026-01-19 09:36:58
/// </summary>
public class ScBillSeqLogRepository : BaseRepository<ScBillSeqLog, ScBillSeqLogDto>
{
public ScBillSeqLogRepository(ISqlSugarRepository<ScBillSeqLog> sqlSugarRepository)
{
Repo = sqlSugarRepository;
}
public override ISugarQueryable<ScBillSeqLog> Queryable(ScBillSeqLogDto dto)
{
return Repo.AsQueryable()
.WhereIF(dto.Id != null && dto.Id > 0, (t) => t.Id == dto.Id)
.WhereIF(!string.IsNullOrEmpty(dto.Billtype), (t) => t.Billtype == dto.Billtype!)
.WhereIF(dto.Datekey != null, (t) => t.Datekey == dto.Datekey)
.WhereIF(dto.Currentnumber != null, (t) => t.Currentnumber == dto.Currentnumber)
;
}
public override ISugarQueryable<ScBillSeqLogDto> DtoQueryable(ScBillSeqLogDto dto)
{
return Repo.AsQueryable()
.WhereIF(dto.Id != null && dto.Id > 0, (t) => t.Id == dto.Id)
.WhereIF(!string.IsNullOrEmpty(dto.Billtype), (t) => t.Billtype == dto.Billtype!)
.WhereIF(dto.Datekey != null, (t) => t.Datekey == dto.Datekey)
.WhereIF(dto.Currentnumber != null, (t) => t.Currentnumber == dto.Currentnumber)
.Select((t) => new ScBillSeqLogDto
{
Id = t.Id
}, true);
}
}
}
其中 BaseRepository 为 ruoyi.net 的后端,代码不贴了,参考:https://gitee.com/wdyday/RuoYi.Net
实现步骤四:服务层
服务层如下:
csharp
using Mapster;
using Microsoft.Extensions.Logging;
using RuoYi.Common.Data;
using RuoYi.Framework.DependencyInjection;
using RuoYi.StockControll.Data.Dtos;
using RuoYi.StockControll.Data.Entities;
using RuoYi.StockControll.Interfaces;
using RuoYi.StockControll.Repositories;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace RuoYi.StockControll.Services
{
/// <summary>
/// 单据序列日志 Service
/// author cgs
/// date 2026-01-19 09:36:58
/// </summary>
public class ScBillSeqLogService : BaseService<ScBillSeqLog, ScBillSeqLogDto>, ITransient, IGenBillNumService
{
private readonly ILogger<ScBillSeqLogService> _logger;
private readonly ScBillSeqLogRepository _scBillSeqLogRepository;
public ScBillSeqLogService(ILogger<ScBillSeqLogService> logger,
ScBillSeqLogRepository scBillSeqLogRepository)
{
BaseRepo = scBillSeqLogRepository;
_logger = logger;
_scBillSeqLogRepository = scBillSeqLogRepository;
}
/// <summary>
/// 查询 单据序列日志 详情
/// </summary>
public async Task<ScBillSeqLog> GetAsync(string id)
{
var entity = await base.FirstOrDefaultAsync(e => e.Billtype == id);
return entity;
}
/// <summary>
/// 查询 单据序列日志 详情
/// </summary>
public async Task<ScBillSeqLogDto> GetDtoAsync(string id)
{
var entity = await base.FirstOrDefaultAsync(e => e.Billtype == id);
var dto = entity.Adapt<ScBillSeqLogDto>();
// TODO 填充关联表数据
return dto;
}
/// <inheritdoc/>
public async Task<string> GenerateBillNumberAsync(string billType = "RK")
{
var date = DateTime.Now.Date;
var billSeqLogDto = await this._scBillSeqLogRepository.GetDtoFirstAsync(new ScBillSeqLogDto
{
Billtype = billType,
Datekey = date
});
if (billSeqLogDto == null)
{
////this._scBillSeqLogRepository.Insert(new ScBillSeqLog
////{
//// Billtype = billType,
//// Datekey = date,
//// Currentnumber = 1
////});
return $"{billType}{date.ToString("yyyyMMdd")}0001";
}
return $"{billType}{date.ToString("yyyyMMdd")}{(billSeqLogDto!.Currentnumber + 1):D4}";
}
/// <inheritdoc/>
public async Task<string> SaveBillNumberAsync(string billType)
{
try
{
var date = DateTime.Now.Date;
int newNumber = 1;
await this.BaseRepo.Repo.Context.AsTenant().BeginTranAsync();
var billSeqLogDto = await this._scBillSeqLogRepository.GetDtoFirstAsync(new ScBillSeqLogDto
{
Billtype = billType,
Datekey = date
});
if (billSeqLogDto != null)
{
newNumber = (billSeqLogDto!.Currentnumber ?? 0) + 1;
int ret = await this._scBillSeqLogRepository.UpdateAsync(new ScBillSeqLog
{
Id = billSeqLogDto.Id,
Billtype = billType,
Datekey = date,
Currentnumber = newNumber
});
}
else
{
bool ret = await this._scBillSeqLogRepository.InsertAsync(new ScBillSeqLog
{
Billtype = billType,
Datekey = date,
Currentnumber = newNumber
});
}
await this.BaseRepo.Repo.Context.AsTenant().CommitTranAsync();
return $"{billType}{date.ToString("yyyyMMdd")}{newNumber:D4}";
}
catch (Exception)
{
this.BaseRepo.Repo.Context.AsTenant().RollbackTran();
throw;
}
}
}
}
实现步骤五:控制器
控制器如下:
csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using RuoYi.Framework;
using RuoYi.StockControll.Services;
namespace RuoYi.StockControll.Controllers
{
/// <summary>
/// 单据序列日志
/// </summary>
[ApiDescriptionSettings("sc")]
[Route("sc/billseqlog")]
public class ScBillSeqLogController : ControllerBase
{
private readonly ILogger<ScBillSeqLogController> _logger;
private readonly ScBillSeqLogService _scBillSeqLogService;
public ScBillSeqLogController(ILogger<ScBillSeqLogController> logger,
ScBillSeqLogService scBillSeqLogService)
{
_logger = logger;
_scBillSeqLogService = scBillSeqLogService;
}
/// <summary>
/// 生成单据编号.
/// </summary>
[HttpGet("genbillnum")]
public async Task<AjaxResult> GenBillNum([FromQuery]string billType)
{
if (string.IsNullOrEmpty(billType) || (billType != "RK" && billType != "CK"))
{
return AjaxResult.Error("参数不正确");
}
var billNum = await _scBillSeqLogService.GenerateBillNumberAsync(billType);
return AjaxResult.Success("操作成功", billNum);
}
/// <summary>
/// 查询单据序列日志列表
/// </summary>
[HttpGet("savebillnum")]
public async Task<AjaxResult> SaveBillNum([FromQuery]string billType)
{
if (string.IsNullOrEmpty(billType) || (billType != "RK" && billType != "CK"))
{
return AjaxResult.Error("参数不正确");
}
var billNum = await _scBillSeqLogService.SaveBillNumberAsync(billType);
return AjaxResult.Success("操作成功", billNum);
}
}
}