Commit 2fc0231c by 孙亚楠

重构算法模块

parent e69a792c
......@@ -3,6 +3,7 @@ var system = require("../../../system");
var settings = require("../../../../config/settings");
var rule =require("../../../utils/invoiceRule/rule");
var Context =require("../../../utils/stateChain/context");
var Calculation = require("../../../utils/strategies/calculation");
class ActionAPI extends APIBase {
constructor() {
super();
......@@ -34,6 +35,16 @@ class ActionAPI extends APIBase {
case "verificationAndCalculation": // 发票试算接口
opResult = await rule.dispatcher(action_body);
break;
case "calculationValueAddedTax": // 计算增值税
let calValueAddedTax = new Calculation(action_body);
let vaInvoice = calValueAddedTax.getInvoice();
opResult = await vaInvoice.doActionValueAddedTax(action_body);
break;
case "calculationAdditionalTax": // 计算附加税
let calAdditionalTax = new Calculation(action_body);
let addinvoice = calAdditionalTax.getInvoice();
opResult = await addinvoice.doActionAddtitionalTax(action_body);
break;
case "saveInvoice": // 发票保存
opResult = await this.applySve.apiSaveInvoice(action_body);
break;
......
......@@ -58,6 +58,14 @@ class System {
data: data,
};
}
static trim(o) {
if(!o) {
return "";
}
return o.toString().trim();
}
static getObject(objpath) {
var pathArray = objpath.split(".");
var packageName = pathArray[0];
......
......@@ -2,6 +2,7 @@
* 1 算法文件命名规则 : calInvoice + 销售方编码
* 2 如果算法中途出现异常 则需直接return
* 3 如果算法顺利执行 返回参数中除包含必须的四个税值外,还需要包含一个累计不含税价字段 字段名称为 "x1"
* 4 此文件必须提供 calcInvoice 和 formatParams 方法
*/
const system = require("../../../system");
......
const applySve = require("../../../service/impl/invoice/applySve");
const common = require("./common");
const system = require("../../../system");
const applyDao = system.getObject("db.invoice.applyDao");
//完成 1090
module.exports.finish=async function (params){
console.log("完成 1090");
let _apply =await common.applyVerification(params);
if(_apply.status==-1){return _apply;}
return applySve.examine1090(params, _apply);
let applyData = {};
applyData.status = common.trim(params.nextStatus);
applyData.customerStatus = common.trim(params.nextStatus);
applyData.id = common.trim(params.id);
// invoiceData.status = common.trim(params.nextStatus);
// invoiceData.id=common.trim(params.id);
await applyDao.db.transaction(async (t) => {
//更新 申请单和发票单
await applyDao.update(applyData, t);
// await this.invoiceDao.update(invoiceData,t);
}).catch(error => {
return system.getResult(null, `系统错误 错误信息 ${error}`);
});
return system.getResultSuccess();
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ module.exports={
invoiced:require('./invoiced'),
//待审核 1060
unAuditedForDeliverer:require('./unAuditedForDeliverer'),
//待处理 1300
//审核失败(平台第二次审核) 1300
unAuditFailForDeliverer:require('./unAuditFailForDeliverer'),
//审核通过 1070
auditSuccessForDeliverer:require('./auditSuccessForDeliverer'),
......
const delivererSve = require("../../../service/impl/invoice/delivererSve");
const system = require("../../../system");
const applyDao = system.getObject("db.invoice.applyDao");
const invoiceDao = system.getObject("db.invoice.invoiceDao");
const delivererDao = system.getObject("db.invoice.delivererDao");
const common = require("./common");
//已邮寄 1080
module.exports.mailed=async (params)=>{
......@@ -6,6 +9,28 @@ module.exports.mailed=async (params)=>{
let obj = await common.delivererVerification(params);
if(obj.status==-1){return obj;}
let _apply = obj._apply;
let _invoice = obj._invoice;
return delivererSve.examine1080(params, _apply, _invoice);
let invoiceData = {}, applyData = {}, delivererData = {};
//更改发票表信息
invoiceData.status = common.trim(params.nextStatus);
invoiceData.mailNo = common.trim(params.mailNo);
invoiceData.id = common.trim(params.id);
//更改申请表信息
applyData.status = common.trim(params.nextStatus);
applyData.id = common.trim(params.id);
//交付商
let _deliverer = await delivererDao.findOne({ id: _apply.delivererId });
delivererData.delivererMailNo = params.mailNo;
delivererData.id = _deliverer.id;
await delivererDao.db.transaction(async (t) => {
//更新申请表状态
await applyDao.update(applyData, t);
//更新发票表状态
await invoiceDao.update(invoiceData, t);
//更新交付商信息
await delivererDao.update(delivererData, t);
}).catch(error => {
return system.getResult(null, `系统错误 错误信息 ${error}`);
});
return system.getResultSuccess();
}
\ No newline at end of file
const strategies = require('./strategiesBase');
/**
* 增值税的环境类
*
* 将注册的说有增值税算法实现都通过环境类调用
* @param{*} businessmenType 销售方类型
* @param{*} params 算法的参数
*/
class ContextAdditionalTax{
constructor(){
console.log("初始化 附加税环境类 ContextAdditionalTax");
}
async doAction (businessmenType,params){
return await strategies[businessmenType]['calcInvoice'](params);
}
}
module.exports=ContextAdditionalTax;
\ No newline at end of file
const system = require("../../../../system");
const Decimal = require('decimal.js');
const PER_TAX = 1; //个税
/**
* 附加税算法
* 注意:1 由于销售方类型不同 所以算法不同 需要对参数验证单独处理
*
* 税率的格式 传去除 %号的数值 比如:
* 96%, 则这个值就传 "taxCostPriRat"=96
*/
module.exports.calcInvoice = (params) => {
try {
//根据 cumulativeProfitOfvalTax 查找对应梯度的个税税率
let calRateRangeResForVal = calRateRange(params.valueAddedTax, params.valAddTaxRange, null);
let {addTaxRat} = calRateRangeResForVal;
//参数验证
verificationParams(params.valueAddedTax,addTaxRat);
//附加税
let res = calAddTax(Number(params.valueAddedTax || 0), Number(addTaxRat || 0));
return system.getResult(res);
} catch (error) {
return system.getResult(-1,`系统错误 错误信息 ${error}`);
}
}
/**
* 计算附加税
* @param {*} valueAddedTax
* @param {*} addTaxRat
*/
let calAddTax=(valueAddedTax, addTaxRat)=>{
if (addTaxRat == 0) {
return 0
} else {
return new Decimal(valueAddedTax).mul(addTaxRat).div(100).toFixed(2);
}
}
/**
* 计算税率值
* @param {*} amount 金额
* @param {*} taxRange 税率范围
* @param {*} type 类型 1:个税 2 增值税
*/
let calRateRange = (amount, taxRange, type=2) => {
let res = {};
if (type === PER_TAX) {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.taxPer = item.rate;
res.quiCalDed = item.quiCalDed;
break;
}
}
} else {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.valAddTaxRat = item.zengzhiRate;
res.addTaxRat = item.fujiaRate;
break;
}
}
}
return res;
}
/**
* 校验各种费率 的合法性
* @param {*} params
* params
*/
let verificationParams = (valueAddedTax,addTaxRat) => {
if(addTaxRat > 100){
system.getResult(-1,`参数错误 销售方类型非法`);
}
}
\ No newline at end of file
const system = require("../../../../system");
const Decimal = require('decimal.js');
/**
* 附加税算法
* 注意:1 由于销售方类型不同 所以算法不同 需要对参数验证单独处理
*
* 税率的格式 传去除 %号的数值 比如:
* 96%, 则这个值就传 "taxCostPriRat"=96
*/
module.exports.calcInvoice = (valueAddedTax,addTaxRat) => {
try {
//参数验证
verificationParams(valueAddedTax,addTaxRat);
//附加税
return calAddTax(valueAddedTax, addTaxRat);
} catch (error) {
return system.getResult(-1,`系统错误 错误信息 ${error}`);
}
}
/**
* 计算附加税
* @param {*} valueAddedTax
* @param {*} addTaxRat
*/
let calAddTax=(valueAddedTax, addTaxRat)=>{
if (addTaxRat == 0) {
return 0
} else {
return new Decimal(valueAddedTax).mul(addTaxRat).div(100).toFixed(2);
}
}
/**
* 校验各种费率 的合法性
* @param {*} params
* params
*/
let verificationParams = (valueAddedTax,addTaxRat) => {
if(params.addTaxRat > 1){
system.getResult(-1,`参数错误 销售方类型非法`);
}
params.addTaxRat = Number(addTaxRat || 0);
params.valueAddedTax = Number(valueAddedTax || 0);
}
\ No newline at end of file
/**
* 导出所有的实现类
*/
module.exports={
//个体工商户
"calInvoice10":require("./calInvoice10"),
//自然人
"calInvoice20":require("./calInvoice20"),
}
\ No newline at end of file
/**
* 定义个增值税的所有接口
*
* 格式:JSON
* key: businessmenType (销售方类型)
* value:Fuction
*
* ps:如果要增加新的增值税算法,需要在此注册
*/
const impl = require('./impl');
module.exports={
//个体工商户
'10': impl.calInvoice10,
//自然人
'20': impl.calInvoice20
}
\ No newline at end of file
var Invoice = require("./invoice");
var system = require("../../system");
var ContextFactory = require("./contextFactory");
/**
* 计算类
*
* 组装的Invoice对象
*/
class Calculation{
constructor(params){
console.log("初始化计算类")
this.contextFactory = new ContextFactory();
this.invoice = new Invoice();
this.init(params);
}
getInvoice(){
return this.invoice;
}
/**
* @param {*} params
* calculation
* @param.calNames 计算种类 是 增值税:valueAddedTax 附加税:additionalTax
*/
init (params){
if(params.calNames instanceof Array){
for (let item of params.calNames) {
if(item=="valueAddedTax"){
this.invoice.setContextValueAddedTax(this.contextFactory.getInstance(system.trim(item)));
}else if(item=="additionalTax"){
this.invoice.setContextAdditionalTax(this.contextFactory.getInstance(system.trim(item)));
}else{
system.getResult(-1,`参数错误 非法的计算类型`);
}
}
}else {
if(system.trim(params.calNames) != "valueAddedTax" && system.trim(params.calNames) != "additionalTax"
&& system.trim(params.calNames) != "individualIncomeTax"){
system.getResult(-1,`参数错误 非法的计算类型`);
}else{
if(params.calNames=="valueAddedTax"){
this.invoice.setContextValueAddedTax(this.contextFactory.getInstance(system.trim(params.calNames)));
}else if(params.calNames=="additionalTax"){
this.invoice.setContextAdditionalTax(this.contextFactory.getInstance(system.trim(params.calNames)));
}else{
system.getResult(-1,`参数错误 非法的计算类型`);
}
}
}
}
}
module.exports=Calculation;
\ No newline at end of file
/**
* 环境工厂类
*/
const ContextAdditionalTax = require("./additionalTax/contextAdditionalTax");
const ContextValueAddedTax = require("./valueAddedTax/contextValueAddedTax");
class ContextFactory {
constructor() {
// this.context = context;
}
getInstance(context) {
let bean = null;
switch (context) {
case "additionalTax":
bean = new ContextAdditionalTax();
break;
case "valueAddedTax":
bean = new ContextValueAddedTax();
break;
default:
console.log("算法环境初始化失败");
bean = null
}
return bean;
}
}
module.exports=ContextFactory;
\ No newline at end of file
/**
* 发票类
*
* 构造器参数: contextAdditionalTax, //附加税上下文
* contextValueAddedTax, //增值税上下文
* contextIndividualIncomeTax //个人所得税上下文
*/
class Invoice {
constructor(contextAdditionalTax,contextValueAddedTax,contextIndividualIncomeTax){
this.contextAdditionalTax = contextAdditionalTax || null;
this.contextValueAddedTax = contextValueAddedTax || null;
// this.contextIndividualIncomeTax = contextIndividualIncomeTax;
}
setContextAdditionalTax(contextAdditionalTax){
this.contextAdditionalTax=contextAdditionalTax;
}
setContextValueAddedTax(contextValueAddedTax){
this.contextValueAddedTax=contextValueAddedTax;
}
//计算附加税
async doActionAddtitionalTax(params){
return await this.contextAdditionalTax.doAction(params.businessmenType,params);
}
//计算增值税
async doActionValueAddedTax(params){
return await this.contextValueAddedTax.doAction(params.businessmenType,params);
}
//计算个人所的税
// async doActionIndividualIncomeTax(params.businessmenType,params){
// }
}
module.exports=Invoice;
\ No newline at end of file
const strategies = require('./strategiesBase');
/**
* 增值税的环境类
*
* 将注册的说有增值税算法实现都通过环境类调用
* @param{*} businessmenType 销售方类型
* @param{*} params 算法的参数
*/
class ContextValueAddedTax{
constructor (){
console.log("初始化 增资税环境类 ContextValueAddedTax");
}
async doAction (businessmenType,params){
return await strategies[businessmenType]['calcInvoice'](params);
}
}
module.exports=ContextValueAddedTax
\ No newline at end of file
/**
* 1 算法文件命名规则 : calInvoice + 销售方编码
* 2 如果算法中途出现异常 则需直接return
* 3 如果算法顺利执行 返回参数中除包含必须的四个税值外,还需要包含一个累计不含税价字段 字段名称为 "x1"
* 4 此文件必须提供 calcInvoice 和 formatParams 方法
*/
const system = require("../../../../system");
const applySve = system.getObject("service.invoice.applySve");
const Decimal = require('decimal.js');
const VAL_TAX = 2; //增值税
const PER_TAX = 1; //个税
/**
* 增值税 businessmen10(个体工商户) 算法实现
* 注意:1 由于销售方类型不同 所以算法不同 需要对参数验证单独处理
*/
module.exports.calcInvoice = async (params) => {
try {
//校验参数
verificationParams(params);
//计算累计不含税价
/**
* 累计不含税价
* @param {*} businessmenId 商户id
* @param {*} businessmenType 商户类型
* @param {*} businessmenCreditCode 统一社会信用代码
* @param {*} taxIncPriRat 不含税价百分比
* @param {*} invoiceAmount 发票总额
* @param {*} type 计算类型 1:个税 2:增值税
* @param {*} valCalWay 增值税计算类型 1:月 2:季度 3:年
* @param {*} perCalWay 个人所的税计算类型 1:月 2:季度 3:年
* @param {*} invoiceTime 格式 YYYY-MM-DD hh:mm:ss
*/
let x3 = await applySve.calAccumulatedPriceExcludingTax(params.businessmenId,params.businessmenType, params.businessmenCreditCode,
params.taxIncPriRat, params.invoiceAmount, VAL_TAX, params.valCalWay, null, params.invoiceTime);
console.log("增值税 累计不含税价总额:" + x3);
//根据 cumulativeProfitOfvalTax 查找对应梯度的个税税率
let calRateRangeResForVal = calRateRange(x3, params.valAddTaxRange, VAL_TAX);
let {valAddTaxRat} = calRateRangeResForVal;
//计算年累计的增值税
let cumulativeProfitOfvalTax = await applySve.calCumulativeProfit(params.businessmenId,params.businessmenType, VAL_TAX, params.valCalWay);
//计算增值税
let valueAddedTax = calValTax(x3, valAddTaxRat, cumulativeProfitOfvalTax);
return system.getResult(valueAddedTax);
} catch (error) {
return system.getResult(-1,`系统错误 错误信息 ${error}`);
}
}
/**
* 计算税率值
* @param {*} amount 金额
* @param {*} taxRange 税率范围
* @param {*} type 类型 1:个税 2 增值税
*/
let calRateRange = (amount, taxRange, type) => {
let res = {};
if (type === PER_TAX) {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.taxPer = item.rate;
res.quiCalDed = item.quiCalDed;
break;
}
}
} else {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.valAddTaxRat = item.zengzhiRate;
res.addTaxRat = item.fujiaRate;
break;
}
}
}
return res;
}
/**
* 计算增值税
* @param {*} x3 //累计不含说价
* @param {*} valAddTaxRat //增值税率
* @param {*} cumulativeProfitOfvalTax //累计缴纳的增值税
*/
let calValTax=(x3, valAddTaxRat, cumulativeProfitOfvalTax)=>{
if (valAddTaxRat == 0) {
return 0;
}
let res = new Decimal(x3).mul(valAddTaxRat).div(100).sub(cumulativeProfitOfvalTax).toFixed(2);
return res;
}
/**
* 校验各种费率 的合法性
* @param {*} params
* params
*/
let verificationParams = (params) => {
if(params.businessmenType != "10"){
system.getResult(-1,`参数错误 销售方类型非法`);
}
if(!params.businessmenCreditCode){
system.getResult(-1,`参数错误 销售方统一社会信用代码非法`);
}
if(params.taxIncPriRat>1){
system.getResult(-1,`参数错误 不含税价百分比不能大于1 例如 3% ,请传 0.03`);
}
if(!params.invoiceAmount){
system.getResult(-1,`参数错误 发票金额非法`);
}
}
\ No newline at end of file
/**
* 1 算法文件命名规则 : calInvoice + 销售方编码
* 2 如果算法中途出现异常 则需直接return
* 3 如果算法顺利执行 返回参数中除包含必须的四个税值外,还需要包含一个累计不含税价字段 字段名称为 "x1"
* 4 此文件必须提供 calcInvoice 和 formatParams 方法
*/
const system = require("../../../../system");
const applySve = system.getObject("service.invoice.applySve");
const Decimal = require('decimal.js');
const VAL_TAX = 2; //增值税
/**
* 增值税 businessmen10(个体工商户) 算法实现
* 注意:1 由于销售方类型不同 所以算法不同 需要对参数验证单独处理
*/
module.exports.calcInvoice = async (params) => {
try {
//校验参数
verificationParams();
//计算累计不含税价
/**
* 累计不含税价
* @param {*} businessmenId 商户id
* @param {*} businessmenType 商户类型
* @param {*} businessmenCreditCode 统一社会信用代码
* @param {*} taxIncPriRat 不含税价百分比
* @param {*} invoiceAmount 发票总额
* @param {*} type 计算类型 1:个税 2:增值税
* @param {*} valCalWay 增值税计算类型 1:月 2:季度 3:年
* @param {*} perCalWay 个人所的税计算类型 1:月 2:季度 3:年
* @param {*} invoiceTime 格式 YYYY-MM-DD hh:mm:ss
*/
let x3 = await applySve.calAccumulatedPriceExcludingTax(params.businessmenId,params.businessmenType, params.businessmenCreditCode,
params.taxIncPriRat, params.invoiceAmount, VAL_TAX, params.valCalWay, null, params.invoiceTime);
console.log("增值税 累计不含税价总额:" + x3);
//根据 cumulativeProfitOfvalTax 查找对应梯度的个税税率
let calRateRangeResForVal = calRateRange(x3, params.valAddTaxRange, VAL_TAX);
let {valAddTaxRat} = calRateRangeResForVal;
//计算年累计的增值税
let cumulativeProfitOfvalTax = await applySve.calCumulativeProfit(params.businessmenId,params.businessmenType, VAL_TAX, params.valCalWay);
//计算增值税
let valueAddedTax = calValTax(x3, valAddTaxRat, cumulativeProfitOfvalTax);
return valueAddedTax;
} catch (error) {
return system.getResult(-1,`系统错误 错误信息 ${error}`);
}
}
/**
* 计算税率值
* @param {*} amount 金额
* @param {*} taxRange 税率范围
* @param {*} type 类型 1:个税 2 增值税
*/
let calRateRange = (amount, taxRange, type) => {
let res = {};
if (type === PER_TAX) {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.taxPer = item.rate;
res.quiCalDed = item.quiCalDed;
break;
}
}
} else {
for (let item of taxRange) {
if (item.minValue <= amount && amount <= item.maxValue) {
res.valAddTaxRat = item.zengzhiRate;
res.addTaxRat = item.fujiaRate;
break;
}
}
}
return res;
}
/**
* 计算增值税
* @param {*} x3 //累计不含说价
* @param {*} valAddTaxRat //增值税率
* @param {*} cumulativeProfitOfvalTax //累计缴纳的增值税
*/
let calValTax=(x3, valAddTaxRat, cumulativeProfitOfvalTax)=>{
if (valAddTaxRat == 0) {
return 0;
}
let res = new Decimal(x3).mul(valAddTaxRat).div(100).sub(cumulativeProfitOfvalTax).toFixed(2);
return res;
}
/**
* 校验各种费率 的合法性
* @param {*} params
* params
*/
let verificationParams = (params) => {
if(params.businessmenType != "10"){
system.getResult(-1,`参数错误 销售方类型非法`);
}
if(!params.businessmenCreditCode){
system.getResult(-1,`参数错误 销售方统一社会信用代码非法`);
}
if(params.taxIncPriRat>1){
system.getResult(-1,`参数错误 不含税价百分比不能大于1 例如 3% ,请传 0.03`);
}
if(!params.invoiceAmount){
system.getResult(-1,`参数错误 发票金额非法`);
}
}
\ No newline at end of file
/**
* 导出所有的实现类
*/
module.exports={
//个体工商户
"calInvoice10":require("./calInvoice10"),
//自然人
"calInvoice20":require("./calInvoice20"),
}
\ No newline at end of file
/**
* 定义个增值税的所有接口
*
* 格式:JSON
* key: businessmenType (销售方类型)
* value:Fuction
*
* ps:如果要增加新的增值税算法,需要在此注册
*/
const impl = require('./impl');
module.exports={
//个体工商户
'10': impl.calInvoice10,
//自然人
'20': impl.calInvoice20
}
\ No newline at end of file
<a name="menu">目录</a>
1. [计算增值税](#calculationValueAddedTax)
1. [计算附加税](#calculationAdditionalTax)
## **<a name="calculationValueAddedTax"> 计算增值税</a>**
[返回到目录](#menu)
##### URL
[/api/op/action/springboard]
#### 参数格式 `JSON`
#### HTTP请求方式 `POST`
``` javascript
// 字段描述 是否必填 默认值/注释
{
"action_process": "sijibao",
"action_type": "calculationValueAddedTax",
"action_body": {
"businessmenCreditCode": "111", //销售方统一社会信用代码 是
"businessmenType":"10", //销售方类型 是 10:个体工商户 20:自然人
"calNames":"valueAddedTax", //计算类型 是 valueAddedTax:增值税
"valCalWay": "1", //计算时间类型 是 1:月 2:季度 3:年
"businessmenId": "12795594625000138", //销售方ID 是
"taxIncPriRat": 0.03, //不含税价百分比 是 如果3% 请填写 0.03 此字段不能大于1
"invoiceTime": "2019-11-26", //发票时间 是
"invoiceAmount": 20000000, //发票金额 单位:分 是
"valAddTaxRange": [ //增值税范围 是
{
"minValue": 0, //最小值 单位:分 是
"zengzhiRate": "3", //增值税百分比 是 如果3%请填写 3
"fujiaRate": "12", //附加税 否
"maxValue": 3000000 //最大值 单位:分 是
}
]
}
}
```
#### 返回结果
```javascript
{
"status": 0,
"msg": "操作成功",
"data": "776699.03", //增值税 单位:分
"bizmsg": "empty",
"requestid": "c19a411e7b5e41a48eb8b4a2d99a2987"
}
```
## **<a name="calculationAdditionalTax"> 计算附加税</a>**
[返回到目录](#menu)
##### URL
[/api/op/action/springboard]
#### 参数格式 `JSON`
#### HTTP请求方式 `POST`
``` javascript
// 字段描述 是否必填 默认值/注释
{
"action_process": "sijibao",
"action_type": "calculationAdditionalTax",
"action_body": {
"businessmenType":"10", //销售方类型 是 10:个体工商户 20:自然人
"calNames":"additionalTax", //计算类型 是 valueAddedTax:增值税
"valueAddedTax": 10000000, //增值税金额 单位:分 是
"valAddTaxRange": [ //增值税范围 是
{
"minValue": 0, //最小值 单位:分 是
"zengzhiRate": "3", //增值税百分比 是 如果3%请填写 3
"fujiaRate": "12", //附加税 否
"maxValue": 3000000 //最大值 单位:分 是
}
]
}
}
```
#### 返回结果
```javascript
{
"status": 0,
"msg": "操作成功",
"data": "1500000.00", //附加税
"bizmsg": "empty",
"requestid": "833f1dfa957d44fabde9ed71652f0677"
}
```
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment