【NumberValidators】增值税发票代码验证

同大陆身份证验证一样,该部分是按照国家增值税发票代码的定制规则,进行发票代码验证,如果需要查验发票信息是否正确,应该通过第三方接口(大约一毛钱查验一次),或者直接上国家税务总局全国增值税发票查验平台进行查验。

目前能识别的增值税发票代码包含以下几类:增值税专用发票增值税普通发票(纸质非卷票)增值税普通发票(卷票)增值税电子普通发票。在类库中,增值税代码验证相关的代码均在NumberValidators.Invoices下,其包含接口定义以及具体实现。

IVATCodeValidator(增值税代码识别接口)定义如下:

    /// <summary>
    /// 增值税发票代码验证接口
    /// </summary>
    public interface IVATCodeValidator <out TResult>: IValidator<TResult>
        where TResult : VATCodeValidationResult, new()
    {
        /// <summary>
        /// 用于验证的字典数据
        /// </summary>
        IValidationDictionary<int, string> Dictionary { get; set; }
        /// <summary>
        /// 生成增值税发票代码
        /// </summary>
        /// <param name="areaNumber">行政区划</param>
        /// <param name="year">年份</param>
        /// <param name="batch">批次</param>
        /// <param name="kind">要生成的发票类型</param>
        /// <returns></returns>
        string GenerateVATCode(int areaNumber, ushort year, ushort batch, VATKind kind);
        /// <summary>
        /// 发票代码验证
        /// </summary>
        /// <param name="vatCode">待验证的发票代码</param>
        /// <param name="kind">要验证的发票类型,不指定则传null</param>
        /// <param name="minYear">允许的最小年份(注:2012年1月1日营改增开始上海试点)</param>
        /// <returns></returns>
        TResult Validate(string vatCode, VATKind? kind = null, ushort minYear = 2012);
    }

增值税发票代码验证定义了两种验证结果
VATCodeValidationResult这是默认验证结果,其定义如下:

    /// <summary>
    /// 增值税发票代码验证结果
    /// </summary>
    public class VATCodeValidationResult : ValidationResult
    {
        /// <summary>
        /// 行政区划代码
        /// </summary>
        public int AreaNumber { get; internal set; }
        /// <summary>
        /// 行政区域名称
        /// </summary>
        public string AreaName { get; internal set; }
        /// <summary>
        /// 发票类型
        /// </summary>
        public VATKind? Category { get; internal set; }
        /// <summary>
        /// 印刷年份
        /// </summary>
        public int Year { get; internal set; }
        /// <summary>
        /// 印刷批次
        /// </summary>
        public int Batch { get; internal set; }
        /// <summary>
        /// 发票联次,仅10位长度和12位长度折叠票发票才有
        /// </summary>
        public int DuplicateNumber { get; internal set; }
    }

VATCode10ValidationResult是在VATCodeValidationResult的基础上,额外定义了发票金额版本,其定义如下:

    /// <summary>
    /// 增值税发票和普通(纸质)专有的验证结果
    /// </summary>
    public class VATCode10ValidationResult : VATCodeValidationResult
    {
        /// <summary>
        /// 发票金额版本号,仅10位长度发票才有
        /// </summary>
        public AmountVersion AmountVersion { get; internal set; }
    }

VATCode12ValidationResult是在VATCodeValidationResult的基础上,额外定义了增值税电子发票的细分类型,其定义如下:

    /// <summary>
    /// 除增值税专项发票外的验证结果
    /// </summary>
    public class VATCode12ValidationResult : VATCodeValidationResult
    {
        /// <summary>
        /// 增值税电子发票细分类型
        /// </summary>
        public ElectronicVATKind? ElectronicVATKind { get; set; }
    }

可根据IsValid来判断验证是否成功,如果验证失败,Errors 属性则包含了验证失败的原因,具体的错误原因列表如下

        /// <summary>
        /// 发票代码为空
        /// </summary>
        public const string Empty = "发票代码为空";
        /// <summary>
        /// 错误的发票代码
        /// </summary>
        public const string Error = "错误的发票代码";
        /// <summary>
        /// 发票年份超出允许的年份范围
        /// </summary>
        public const string YearOutOfRange = "发票年份超出允许的年份范围{0} ~ {1}";
        /// <summary>
        /// 发票发行区域识别失败
        /// </summary>
        public const string InvalidArea = "发票发行区域识别失败";
        /// <summary>
        /// 无效的发票类别
        /// </summary>
        public const string InvalidKind = "无效的发票类别";
        /// <summary>
        /// 发票类别错误,无法生成发票代码
        /// </summary>
        public const string GenerateWrongKind = "发票类别错误,无法生成发票代码";
        /// <summary>
        /// 无效实现
        /// </summary>
        public const string InvalidImplement = "未能找到或无效的 {0} 位发票代码实现";
        /// <summary>
        /// 长度不符
        /// </summary>
        public const string LengthOutOfRange = "发票代码非 {0} 位";

因为目前类库中已经完整收集了所有发票代码中支持的行政区划编号(可在航信官网上查看都有哪些区域存在税务局),所以暂时不再需要自行传递Dictionary来进行支持区域的修正。

目前IVATCodeValidator包含VATCode10Validator以及VATCode12Validator两种具体实现

  • VATCode10Validator 对应长度为10的发票代码,包含增值税专用发票、增值税普通发票
  • VATCode12Validator 对应长度为12的发票代码,包含增值税普通发票、增值税普通发票(卷票)、增值税电子普通发票
  • VATCodeValidatorHelper 为静态类,用于辅助验证,其内部简单的封装了按发票代码长度调用对应的IVATCodeValidator实现

使用例子如下

            Console.WriteLine("***增值税发票***");
            var vat10Validator = new VATCode10Validator();
            var vat12Validator = new VATCode12Validator();
            Console.WriteLine("随机的增值税发票:" + vat10Validator.GenerateRandomNumber());
            Console.WriteLine("生成指定的增值税专用发票:" + vat10Validator.GenerateVATCode(3700, 2017, 1, Invoices.VATKind.Special));
            Console.WriteLine("生成指定的10位增值税普通发票:" + vat10Validator.GenerateVATCode(1100, 2017, 2, Invoices.VATKind.Plain));
            Console.WriteLine("生成指定的12位增值税普通发票:" + vat12Validator.GenerateVATCode(1100, 2018, 6, Invoices.VATKind.Plain));
            Console.WriteLine("随机的增值税电子/卷票/普票:" + vat12Validator.GenerateRandomNumber());
            string[] vatArr = { "031001600311", "3100153130", "011001800304" };
            foreach (var vat in vatArr)
            {
                var valid = VATCodeValidatorHelper.Validate(vat, minYear: 2012);
                Console.WriteLine("{0}验证结果:{1} 类型{2} 行政区划名称({3}) 验证结果类型:{4}", vat, valid.IsValid, valid.Category, valid.AreaName, valid);
            }

PS:目前1.0版本中VATCode12Validator未支持12位的增值税普通发票以及收费公路通行费增值税电子发票,如果需要支持,需从git上下载代码后自行生成dll

posted on 2018-08-22 14:42  胡萝卜星星  阅读(900)  评论(1编辑  收藏  举报

导航