前言:其实唯品会的接口使用还是非常简单,开发者可能有疑问还是他们的接口流程问题和如何与自家的ERP对接起来。这篇文章是在记录自己对接唯品会的过程的理解,也因为是第一次接触唯品会,刚开始疑问还挺多的,网上也没啥这类问题的解答,都得自己去研究实验,有需要的可参考参考。
PS:唯品会的开放平台文档N久没更新,有些地方说的已经不存在或者已经变更为其他方式,总之就是看文档会让你产生很多误区,但是你也没地方有资料看了。另外他们的沙箱,提供的数据是全是死的不变的,而且跟实际数据会差很远,我是按自己的理解纯码代码上正式测的。(今年更新了,我这是去年对接的,今年SDK为应对新型肺炎疫情 新增了一些字段,没更新的同志们要注意了)
一、准备工作-秘钥的获取
1.创建应用:这很简单注册成为开发者,供应商认证成功过后就可以创建应用,我创建的是 自研应用 。这里要说明一下,创建应用时填写的选项也是有学问的:
合作类型 -- JIT和直发:
JIT模式:我是服装公司(唯品会称呼我为供应商),客户下单了,由唯品会下指要供应商发货,发到唯品会他们自己的指另的仓库,然后由唯品会的人发货给客户,所以快递费要我们自己出。
直发:客户下单了,由我(供应商)直接发货给客户。PS:这块的接口他们已经弃用了,但是开发者平台没更新。这块的业务已经改为JITX业务了,接口也是另一套。这里也要说明一下:即使是我们自己发货,但你也不要以为你就拥有了自己的这一批客户源,唯品会对用户信息保密异常严格,只有他们自己知道用户信息。我们供应商拿到的全是加密的。
JITX业务说明:因为是由我们直接发货给客户,并且用户信息是保密的,所以承运商+快递单号+快递费 全是由唯品承担。关于退货,这也跟淘宝天猫不一样,商家也不需要处理那些繁杂的退货申请,客户退货也是直接退给唯品的仓库,然后他们处理之后再发回给商家这里。所以 是没有 退货申请 接口的。
PS:关于退货这块,我咨询过公司的业务人员,说是唯品后台会有财务报表,这些报表就是最终生效的订单,我们的业务人员会定期筛选出来导入到我们的ERP系统,这才跟ERP的销售那块的账对上,所以换句话说不用技术人员 再去另外弄销售单。这里不能一概而论所有公司都这样做,还是得看各个公司的业务模式咋样才能决定自家要怎么处理这块。
回调URL:这个就是正式上使用接口必要的参数:access_token,就是需要通过这个获取到。这里要说下,access_token有效期3个月,你只能单独通过https://auth.vip.com/oauth2/authorize?client_id=appKey&response_type=code&redirect_uri=回调URL
这个链接输入VOP系统的账号密码授权之后,唯品会通过你设置的回调地址,将access_token和refresh_token,expires_in,refresh_expires_time返回给你,你需要记录下来,配置到系统中的某个地方,调唯品的接口的时候就用它。那3个月过期之后呢,要么在通过refresh_token(有效期1年)调用唯品的刷新access_token的接口,要么就再一次通过授权这个方式获取token。1年之后吧,就真的需要在一次通过授权去获取access_token了,这就需要你及时更新到才行。
2.拿取 App Key+App Secret
3.配置白名单:即你调用唯品接口的程序发布的服务器IP,
二、码代码
1.0 先从官方将唯品的sdk下载下来,他们封装好了所有接口的请求(包括model,request),链接
JIT和JITX是可以同时跑的,我是先把JIT的对接好了之后,再去对接JITX的。先上JIT的代码吧,因为JITX的稍微要麻烦点,而且JITX的运行跟商家的运营政策不同而编写规则也不同,毕竟 这是要对接自家的ERP系统的。
PS:这里注意,唯品的接口会时不时来个超时,所以要避免这种情况的发生导致我们错过点拉不到数据或拉不全
1.1 唯品的接口里有自己的一套仓库编码和快递承运商编码,我们使用人家的接口的时候 传递的参数值 也必须是 唯品规定的一套枚举值。所以若是 要和自家的ERP关联起来,就要 配置与 唯品一样的或者能2边关联上的 仓库编码和承运商编码
获取承运商的接口 :vipapis.delivery.JitDeliveryService-1.0.0#getCarrierList
在官网里的这个接口的下面就有仓库的枚举值列表
1.2 先上每个接口必须要用到的公共方法和配置参数:
//这些使用唯品接口必要的参数,我是配置在config里;分别对应 上一节说的 应用的key, 秘钥 ,商户ID ,唯品接口运行地址
private string AppKey = System.Configuration.ConfigurationManager.AppSettings["VPH_AppKey"].ToString();
private string AppSecret = System.Configuration.ConfigurationManager.AppSettings["VPH_AppSecret"].ToString();
private string VendorId = System.Configuration.ConfigurationManager.AppSettings["VPH_VendorId"].ToString();
private string AppUrl = System.Configuration.ConfigurationManager.AppSettings["VPH_AppUrl"].ToString();
#region 拿Token
//令牌信息,token3个月过期,中间一年可通过refresh_token拿取新的token,一年之后就要重新授权
private static AccessToken _token;
public class AccessToken
{
public AccessToken(string code, DateTime time, string rcode, DateTime rTime)
{
this.Access_Token = code;
this.Time = time;
this.Refresh_Token_Time = rTime;
this.Refresh_Token = rcode;
}
public string Access_Token { get; set; }
public DateTime Time { get; set; }
public string Refresh_Token { get; set; }
public DateTime Refresh_Token_Time { get; set; }
}
private string GetAccessToken()
{
if (_token == null)
{
var token = System.Configuration.ConfigurationManager.AppSettings["VPH_AccessToken"].ToString();//AccessToken授权拿到的记录在config中
var outTime = System.Configuration.ConfigurationManager.AppSettings["VPH_AccessToken_OutTime"].ToString();//AccessToken过期时间授权拿到的记录在config中
var reToken = System.Configuration.ConfigurationManager.AppSettings["VPH_ReAccessToken"].ToString();//refresh_token授权拿到的记录在config中
var reOutTime = System.Configuration.ConfigurationManager.AppSettings["VPH_ReAccessToken_OutTime"].ToString();//refresh_expires_time授权拿到的记录在config中
_token = new AccessToken(token, Convert.ToDateTime(outTime), reToken, Convert.ToDateTime(reOutTime));
}
var time = _token.Time;
if (time != null && time > DateTime.Now.AddDays(1))
{
return _token.Access_Token;
}
else if (_token.Refresh_Token_Time > DateTime.Now.AddDays(1))
{
OauthServiceClient client = new OauthServiceClient();
try
{
ClientInvocationContext instance = new ClientInvocationContext();
instance.SetAppKey(AppKey);
instance.SetAppSecret(AppSecret);
instance.SetAppURL(AppUrl);
instance.SetAccessToken(_token.Access_Token);
client.SetClientInvocationContext(instance);
vipapis.oauth.RefreshTokenRequest request = new vipapis.oauth.RefreshTokenRequest();
request.SetRefresh_token(_token.Refresh_Token);
request.SetClient_id(AppKey);
request.SetClient_secret(AppSecret);
var ip = System.Configuration.ConfigurationManager.AppSettings["VPH_WhiteIP"].ToString();//上一节里配置的IP白名单
request.SetRequest_client_ip(ip);
vipapis.oauth.RefreshTokenResponse token = client.refreshToken(request);
_token.Access_Token = token.GetAccess_token();
_token.Time = (DateTime)token.GetExpires_time();
_token.Refresh_Token = token.GetRefresh_token();
_token.Refresh_Token_Time = (DateTime)token.GetRefresh_expires_time();
return _token.Access_Token;
}
catch (OspException e)
{
//LogHelper.WriteLog(new Exception("Token拿取失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "GetAccessToken");
throw e;
}
}
else
{
//已过期需要用户手动授权拿到初始化值更新AccessToken的默认值之后才能用
//LogHelper.WriteLog(new Exception("Token拿取失败:已完全过期,需要用户手动授权"), "GetAccessToken");
throw new OspException("Token已完全过期", new Exception());
}
}
#endregion
1.3 JIT:唯品会会分几个档次下发发货指令(唯品称为:PO单),我们这是每天定时2个点上午和下午,所以我的代码每天在这指令的2个点各跑一次就行。
在唯品接口里执行 【拉取PO单,生成拣货单】2个接口,然后根据当前生成的拣货单 在我们的ERP中生成对应的配货单,即通知我们(商家)的仓库那边,有多少个单需要发什么货。这些事物我在一个接口里写完了
//DBHelper 类,封装好的执行数据库的类,可以自己写,这很简单
//LogHelper 类,封装好的日志记录的类
public HttpResponseMessage VOP_JIT_GetPoList()
{
JitDeliveryServiceClient client = new JitDeliveryServiceClient();
try
{
ClientInvocationContext instance = new ClientInvocationContext();
instance.SetAppKey(AppKey);
instance.SetAppSecret(AppSecret);
instance.SetAppURL(AppUrl);
instance.SetAccessToken(GetAccessToken());
client.SetClientInvocationContext(instance);
//获取未拣货订单,然后生成拣货单
int page = 1;
while (true)
{
#region 获取PO单,超时时最多执行3次
int p_tryTimes = 1;
vipapis.delivery.GetPoListResponse unPickList = null;
while (true)
{
try
{
unPickList = client.getPoList(null, null, null, null, null, VendorId, null, null, null, null, page, 100, null, null);
break;
}
catch (OspException e)
{
if (p_tryTimes < 3)
{
p_tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-拉取PO单失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JIT_CreatePick");
throw e;
}
}
}
#endregion
//根据拉取的PO单,创建拣货单
foreach (var item in unPickList.GetPurchase_order_list())
{
if (Convert.ToInt32(item.GetNot_pick()) > 0)
{
#region 接口超时时可设置再次尝试执行三次
int tryTimes = 1;
while (true)
{
try
{
var re = client.createPick(item.GetPo_no(), Convert.ToInt32(VendorId), null, null, null);
break;
}
catch (OspException e)
{
if (tryTimes < 3)
{
tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-生成拣货单失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JIT_CreatePick");
throw e;
}
}
}
#endregion
}
}
if (unPickList.GetTotal() == null || unPickList.GetTotal() <= (page * 100))
{
break;
}
page++;
}
//获取拣货单列表
int page_pick = 1;
while (true)
{
#region 拉取现有拣货单,接口超时时可设置再次尝试执行三次
vipapis.delivery.GetPickListResponse pickList = null;
int tryTimes = 1;
while (true)
{
try
{
pickList = client.getPickList(Convert.ToInt32(VendorId), null, null, null, null, null, null, null, null, null, null, null, null, page_pick, 100, null);
break;
}
catch (OspException e)
{
if (tryTimes < 3)
{
tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-获取拣货单失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JIT_CreatePick");
throw e;
}
}
}
#endregion
var pickData = pickList.GetPicks().Where(w => w.GetDelivery_status() == 0).OrderBy(s => Convert.ToDateTime(s.GetCreate_time()));//0状态为未送货,1已发货
foreach (var pick in pickData)
{
//判断ERP是否已配货:VPH_JITNOTICETOPICK,ERP中我自己建好的表,装在JIT订单的记录的表,这里你自己参考就好,不必纠结这里
int count1 = Convert.ToInt32(DBHelper.OracleExecuteScalar("select count(0) from VPH_JITNOTICETOPICK where PICKNO = '" + pick.GetPick_no() + "'", DBHelper.ConString));
if (count1 > 0)
{
//LogHelper.WriteLog(new Exception("PICK单:" + pick.GetPick_no() + "已存在ERP系统"), "VOP_JIT_GetPoList");
continue;
}
//拿明细,组装数据
var resultData = new List();
var goodsData = new List();
var pickDetail = new List();
var isNormalPick = true;
int page_pickDetail = 1;
while (true)
{
#region 拉取拣货单明细数据,接口超时时可设置再次尝试执行三次
vipapis.delivery.PickDetail detail = null;
int p_tryTimes = 1;
while (true)
{
try
{
detail = client.getPickDetail(pick.GetPo_no(), Convert.ToInt32(VendorId), pick.GetPick_no(), page_pickDetail, 100, null);
break;
}
catch (OspException e)
{
if (p_tryTimes < 3)
{
p_tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-获取拣货单明细失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JIT_CreatePick");
throw;
}
}
}
#endregion
resultData.AddRange(detail.GetPick_product_list());
if (detail.GetTotal() == null || detail.GetTotal() <= (page_pickDetail * 100))
{
break;
}
page_pickDetail++;
}
if (resultData.Count() > 0)
{
//拿取拣货单的明细数据,接下来就是在我们的ERP中进行操作了
//1.生成配货单,同时将此单推送给仓库(发货端同事),每个拣货单对应一个配货单,我设计的VPH_JITNOTICETOPICK 表中存的就是 配货单与拣货单的关联数据
}
}
if (pickList.GetTotal() == null || pickList.GetTotal() <= (page_pick * 100))
{
break;
}
page_pick++;
}
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "0", msg = "请求成功!" }), Encoding.UTF8, "application/json"),
};
}
catch (OspException e)
{
//LogHelper.WriteLog(new Exception("拉取PO单生成配单失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JIT_GetPoList");
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "1", msg = "请求失败!" }), Encoding.UTF8, "application/json"),
};
}
}
JIT的对接我到此就做完了,后面其实还有 发货 要做,但是这里我没有继续做下去了。一因为 JIT的单据一天不会有几个,每个档期一天2个点,我们这只有7个仓库,每个仓库一个拣货单。一天一个档期也就最多14个单。加上 由于货品库存的问题,会经常调整最终 发给唯品的货品清单,所以这里下面就没对接自动发货的接口了。每个公司业务不一样,实现的逻辑和步骤不一样。
1.4 JITX ,这个得分2块来操作,
第一块:获取带寻仓订单->反馈寻仓结果->获取JITX订单拉到ERP中,占库存生成对应的配货单
这一步我这边因为是 单渠道 发货所以没有对接这一块,唯品那边若是单渠道的就会自动给分配好发货仓库。若是需要做多渠道发货的,那可真就有点儿麻烦。下面就是这一块的流程图
//拉取待寻仓单,确定哪个仓库发货
public ActionResult VOP_JITX_Delivery()
{
JitXServiceClient client = new JitXServiceClient();
try
{
ClientInvocationContext instance = new ClientInvocationContext();
instance.SetAppKey(AppKey);
instance.SetAppSecret(AppSecret);
instance.SetAppURL(AppUrl);
instance.SetAccessToken(GetAccessToken());
client.SetClientInvocationContext(instance);
int page = 1;
int pageSize = 50;
while (true)
{
//获取待寻仓列表
var oparam = new vipapis.jitx.GetDeliveryOrdersRequest();
oparam.SetVendor_id(Convert.ToInt32(VendorId));
DateTime dateStart = new DateTime(1970, 1, 1, 8, 0, 0);
oparam.SetStart_time(Convert.ToInt32((DateTime.Now.AddMinutes(-31) - dateStart).TotalSeconds));
oparam.SetEnd_time(Convert.ToInt32((DateTime.Now.AddMinutes(-1) - dateStart).TotalSeconds));
var status = new List();
status.Add("NEW");
oparam.SetStatus_list(status);
oparam.SetLimit(pageSize);
oparam.SetPage(page);
#region 接口超时时可设置再次尝试执行三次
int tryTimes = 1;
vipapis.jitx.GetDeliveryOrdersResponse dOrder = null;
while (true)
{
try
{
dOrder = client.getDeliveryOrders(oparam);
break;
}
catch (OspException e)
{
if (tryTimes < 3)
{
tryTimes++;
continue;
}
else
{
throw e;
}
}
}
#endregion
//--------------反馈寻仓结果--------------------
var orders = dOrder.GetDelivery_orders();
List fbkResult = new List();//一次只能50个
foreach (var o in orders)
{
if (o.GetStatus() == "NEW")
{
var sendwarehouse = o.GetAvailable_warehouses().FirstOrDefault();//这里要注意,若是你公司是多渠道发货的话,这里可供分配发货的仓库就会有多个,这里的逻辑看自家公司业务上要怎么决定发货制度。我这里默认选择第一个渠道,因为我们这是单渠道的发货
if (sendwarehouse == null)
{
//LogHelper.WriteLog(new Exception("唯品JITX寻仓配仓失败:" + o.GetOrder_sn() + "订单没有可分配的仓!"), "VOP_JITX_Delivery");
continue;
}
var fdk = new vipapis.jitx.FeedbackDeliveryResult();
fdk.SetOrder_sn(o.GetOrder_sn());
fdk.SetFeedback_status("SUCCESS");
fdk.SetWarehouse(sendwarehouse);//我们只有一个仓库,那就默认选取第一个
fbkResult.Add(fdk);
}
}
var fparam = new vipapis.jitx.FeedbackDeliveryResultRequest();
fparam.SetVendor_id(Convert.ToInt32(VendorId));
fparam.SetResults(fbkResult);
#region 接口超时时可设置再次尝试执行三次
int tryTimes_fd = 1;
while (true)
{
try
{
var fdbresult = client.feedbackDeliveryResult(fparam);//获取反馈结果
break;
}
catch (OspException e)
{
if (tryTimes_fd < 3)
{
tryTimes_fd++;
continue;
}
else
{
throw e;
}
}
}
#endregion
if (dOrder.GetTotal() == null || dOrder.GetTotal().Value <= (page * pageSize))
{
break;
}
page++;
}
return Json(new { code = 0, msg = "请求成功" });
}
catch (OspException e)
{
//LogHelper.WriteLog(new Exception("唯品JITX寻仓配仓失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Delivery");
return Json(new { code = 1, msg = "请求失败" });
}
finally
{
if (conn != null) conn.Dispose();
}
}
//获取JITX订单,拉到ERP中,并生成ERP中的配货单之类的单据,推到仓库那边的系统,他们就能按照这些单据来扫面出库
public HttpResponseMessage VOP_JITX_Order()
{
JitXServiceClient client = new JitXServiceClient();
try
{
ClientInvocationContext instance = new ClientInvocationContext();
instance.SetAppKey(AppKey);
instance.SetAppSecret(AppSecret);
instance.SetAppURL(AppUrl);
instance.SetAccessToken(GetAccessToken());
client.SetClientInvocationContext(instance);
var oparam = new vipapis.jitx.GetOrdersRequest();
oparam.SetVendor_id(Convert.ToInt32(VendorId));
var status = new List();
status.Add("10");//未发货 97_10未发已取消
oparam.SetOrder_status(status);
DateTime dateStart = new DateTime(1970, 1, 1, 8, 0, 0);
//唯品的接口要求查询时间间隔不能超过30分钟
oparam.SetStart_time(Convert.ToInt32((DateTime.Now.AddMinutes(-31) - dateStart).TotalSeconds));
oparam.SetEnd_time(Convert.ToInt32((DateTime.Now.AddMinutes(-1) - dateStart).TotalSeconds));
#region 接口超时时可设置再次尝试执行三次
int tryTimes = 1;
vipapis.jitx.GetOrdersResponse dOrder = null;
while (true)
{
try
{
dOrder = client.getOrders(oparam);
break;
}
catch (OspException e)
{
if (tryTimes < 3)
{
tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-拉取JITX订单失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Order");
throw e;
}
}
}
#endregion
//
foreach (var item in dOrder.GetOrders())
{
//生成配货单,要判断货品是否在ERP中存在与否
}
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "0", msg = "请求成功!" }), Encoding.UTF8, "application/json"),
};
}
catch (OspException e)
{
//LogHelper.WriteLog(new Exception("唯品JITX拉单配货失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Order");
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "1", msg = "请求失败!" }), Encoding.UTF8, "application/json"),
};
}
}
第二块:发货模块
我主要是对接了这一方面的,要实现自动发货,首先程序就要知道哪个订单已经打包发出去了,程序才能通过唯品接口更新唯品那边的订单发货状态。我这边有一套单据回传逻辑,若是仓库那边已扫描出单,就会到ERP这边回传状态回来,然后我就是 定时轮查 那些在ERP中未发货订单状态是不是 已打包发货,然后筛选出来之后 就通过vipapis.jitx.JitXService-1.0.0#ship 接口更新唯品系统上订单的发货状态。
这一块上面说了,邮费是唯品出的,所以面单什么的也必须按照他们的模板打印,最好的最方便发货人员操作的就是 在发货人员一边按单扫面出库的时候,可以同时点打印面单。你可以自己组装模板,通过vipapis.jitx.JitXService-1.0.0#getPrintTemplate这个接口拿取订单的快递字段,我想唯品的人会发你各种快递的模板的。不想自己组装的话那就是用vipapis.jitx.JitXService-1.0.0#getOrderLabel 获取 html面单。
//JITX自动发货
public HttpResponseMessage VOP_JITX_Ship()
{
JitXServiceClient client = new JitXServiceClient();
try
{
ClientInvocationContext instance = new ClientInvocationContext();
instance.SetAppKey(AppKey);
instance.SetAppSecret(AppSecret);
instance.SetAppURL(AppUrl);
instance.SetAccessToken(GetAccessToken());
client.SetClientInvocationContext(instance);
//拿取ERP中所有未发货的订单,这里不要照抄啊,每家ERP公司都不一样的数据,我这里只是我这边的情况,程序员搬砖也要会举一反三
List listData = DBHelper.OracleExecuteRead(@"", DBHelper.ConString);
//第一步:先通过从唯品线上查出这些订单,有没有被客户取消的,若取消了则作废ERP配货单+同时作废仓库系统的对应订单
var unsendparam = new vipapis.jitx.GetOrdersByOrderSnRequest();
unsendparam.SetVendor_id(Convert.ToInt32(VendorId));
var unOds = listData.Where(a => a.SENDGUID == null || a.SENDGUID == "").Select(s => new { ORDERSN = (string)s.ORDERSN, SENDNOTICEGUID = (string)s.SENDNOTICEGUID, SENDNOTICENO = (string)s.SENDNOTICENO }).OrderBy(a => a.ORDERSN);
if (unOds.Count() > 0)
{
var times = Math.Floor(unOds.Count() / 500.0);
for (int i = 0; i <= times; i++)
{
var os = unOds.Select(a => a.ORDERSN).Skip(500 * i).Take(500).ToList();
unsendparam.SetOrder_sns(os);//不能超过500个
vipapis.jitx.GetOrdersResponse unsendOrder = null;
#region 拉取订单-接口超时时可设置再次尝试执行三次
int tryTimes_un = 1;
while (true)
{
try
{
unsendOrder = client.getOrdersByOrderSn(unsendparam);
break;
}
catch (OspException e)
{
if (tryTimes_un < 3)
{
tryTimes_un++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-请求getOrdersByOrderSn接口失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Ship");
break;
}
}
}
#endregion
if (unsendOrder != null)
{
var canselOrder = unsendOrder.GetOrders().Where(a => a.GetOrder_status() == "97_10").Select(d => d.GetOrder_sn());
var canselOrder_T = unOds.Where(a => canselOrder.Contains(a.ORDERSN));
foreach (var item in canselOrder_T)
{
//LogHelper.WriteLog(new Exception(item.SENDNOTICENO + " 请求取消订单"), "VOP_JITX_Ship");
//作废ERP配货单+同时作废仓库系统的对应订单
}
}
}
}
//第二步:仓库已配完货的,则更新唯品的发货状态
var sendData = listData.Where(a => a.SENDGUID != null && a.SENDGUID != "");
foreach (var item in sendData)
{
try
{
//拉取唯品单据的最新状态
var oparam = new vipapis.jitx.GetOrdersByOrderSnRequest();
oparam.SetVendor_id(Convert.ToInt32(VendorId));
var ods = new List();
ods.Add(item.ORDERSN);
oparam.SetOrder_sns(ods);
vipapis.jitx.GetOrdersResponse dOrder = null;
#region 拉取当前订单-接口超时时可设置再次尝试执行三次
int tryTimes = 1;
while (true)
{
try
{
dOrder = client.getOrdersByOrderSn(oparam);
break;
}
catch (OspException e)
{
if (tryTimes < 3)
{
tryTimes++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-请求getOrdersByOrderSn接口失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Ship");
break;
}
}
}
#endregion
if (dOrder == null)
{
continue;
}
var order = dOrder.GetOrders().FirstOrDefault();
if (order.GetOrder_status() == "10")//未发货10,即可进行发货
{
var sparam = new vipapis.jitx.ShipRequest();
sparam.SetVendor_id(Convert.ToInt32(VendorId));
var ship = new List();
var sigelship = new vipapis.jitx.Ship();
sigelship.SetOrder_sn(item.ORDERSN);
sigelship.SetDelivery_warehouse(order.GetDelivery_warehouse());
sigelship.SetTotal_package(1);
string detailSql = string.Format(@"", item.SENDGUID);//查询发货单的明细
var detailList = DBHelper.OracleExecuteRead(detailSql, DBHelper.ConString);
var developer = System.Configuration.ConfigurationManager.AppSettings["VPH_Developer"].ToString();
var f360Detail = detailList.Select(a => a.BARCODE + "-" + a.QTY);
var vphDetail = order.GetOrder_goods().Select(a => a.GetBarcode() + "-" + a.GetQuantity());
if (f360Detail.Count() != vphDetail.Count())
{
string sql_ = string.Format(@"insert into J_Transfer_Notice(Type,Code,Date,SendUser,Send,SendText,Modify) select '唯品配货通知',replace(newid(), '-', ''),Getdate(),'" + developer + "',0,'{0}',Getdate() ", item.ORDERSN + "唯品JITX发货失败:发货明细与唯品订单明细不一致");
DBHelper.ExecuteNonQuery(sql_, DBHelper.BusinessConection);
LogHelper.WriteLog(new Exception(item.ORDERSN + "唯品JITX发货失败:发货明细与唯品订单明细不一致"), "VOP_JITX_Ship");
continue;
}
var verfityData = f360Detail.Union(vphDetail);
if (verfityData.Count() != vphDetail.Count())
{
string sql_ = string.Format(@"insert into J_Transfer_Notice(Type,Code,Date,SendUser,Send,SendText,Modify) select '唯品配货通知',replace(newid(), '-', ''),Getdate(),'" + developer + "',0,'{0}',Getdate() ", item.ORDERSN + "唯品JITX发货失败:发货明细与唯品订单明细不一致");
DBHelper.ExecuteNonQuery(sql_, DBHelper.BusinessConection);
LogHelper.WriteLog(new Exception(item.ORDERSN + "唯品JITX发货失败:发货明细与唯品订单明细不一致"), "VOP_JITX_Ship");
continue;
}
#endregion
var detail = new List();
var p = new vipapis.jitx.Package();
p.SetBox_no(1);
DateTime dateStart = new DateTime(1970, 1, 1, 8, 0, 0);
p.SetOqc_date(Convert.ToInt32((Convert.ToDateTime(item.CREATEDDATE) - dateStart).TotalSeconds));
p.SetTransport_no(order.GetTransport_no());
p.SetPackage_no(item.SENDSHEETID);
List goods = detailList.Select(a =>
{
var temp = new vipapis.jitx.PackageDetail();
temp.SetBarcode(a.BARCODE);
temp.SetQuantity((int)a.QTY);
return temp;
}).ToList();
p.SetDetails(goods);
detail.Add(p);
sigelship.SetPackages(detail);
ship.Add(sigelship);
sparam.SetShips(ship);
vipapis.jitx.ShipResponse re = null;
#region 发货-接口超时时可设置再次尝试执行三次
int tryTimes_ship = 1;
while (true)
{
try
{
re = client.ship(sparam);
break;
}
catch (OspException e)
{
if (tryTimes_ship < 3)
{
tryTimes_ship++;
continue;
}
else
{
//LogHelper.WriteLog(new Exception("3次-请求ship接口失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Ship");
break;
}
}
}
#endregion
if (re == null)
{
continue;
}
if (re.GetSuccess_num() > 0)
{
//发货成功,则更新ERP对应订单的状态
}
else
{
string error = "唯品订单号:" + item.ORDERSN + " 发货单号:" + item.SENDSHEETID + " 更新唯品订单发货状态失败:" + re.GetFailed_list().FirstOrDefault().GetMsg();
//LogHelper.WriteLog(new Exception("唯品JITX发货失败:" + error), "VOP_JITX_Ship");
}
}
else if (order.GetOrder_status() == "97_10")//未发取消,已配完货客户取消
{
//这里上面也说了,其实要是已出库了,就只能在重新做入库了,这个得由发货人员去手动入单,因为实物在他们那边,系统若是做自动入库,很容易造成库存差异
}
}
catch (Exception ex)
{
string error = "唯品订单号:" + item.ORDERSN + " 发货单号:" + item.SENDSHEETID + " 错误信息:" + ex.Message;
//LogHelper.WriteLog(new Exception("唯品JITX发货失败:" + error), "VOP_JITX_Ship");
continue;
}
}
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "0", msg = "请求成功!" }), Encoding.UTF8, "application/json"),
};
}
catch (OspException e)
{
//LogHelper.WriteLog(new Exception("唯品JITX发货失败:" + "错误编号:" + e.GetReturnCode() + " 返回信息:" + e.GetReturnMessage() + " 系统错误信息:" + e.Message + " 最近一次调用的sign:" + (client.GetClientInvocationContext() == null ? "" : client.GetClientInvocationContext().GetLastInvocation().GetSign())), "VOP_JITX_Ship");
return new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(new { code = "1", msg = "请求失败!" }), Encoding.UTF8, "application/json"),
};
}
}
到此,唯品的JIT和JITX都对接完了,再次申明:关于与ERP方面的对接仅限于参考,我提供我这边的实际情况对接出来的经验与思路。同志们你们要自己做自家公司的对接请按实际情况修改代码。
总结:其实使用唯品的接口还是异常简单的,真的简单,我这是从没在唯品上买过东西的人,有时候会把淘宝的逻辑套在他们身上想,发现很多时候都会想多了,毕竟是2个不同的平台。先试着问唯品的技术人员,然后再来自己实际尝试,才会得出能解释的答案,也不要一不懂就问,先自己尝试之后再去问,毕竟人家也很忙,若是发现自己问了个弱智白痴问题,那就尴尬了。
以上纯属个人独自研究成果,仅供参考,转载请注明出处