小程序发货信息录入

This commit is contained in:
lxy 2025-03-13 14:57:49 +08:00
parent bb3fe6cd49
commit 2692e2d6fc
29 changed files with 806 additions and 788 deletions

View File

@ -7,7 +7,6 @@ export type AspectDict<ED extends EntityDict> = {
withdrawAccountId?: string; withdrawAccountId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>;
getMpShipState: (params: { getMpShipState: (params: {
depositId?: string; shipId: string;
orderId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>;
}; };

View File

@ -5,6 +5,5 @@ import { BRC } from '../types/RuntimeCxt';
* @param context * @param context
*/ */
export declare function getMpShipState(params: { export declare function getMpShipState(params: {
depositId?: string; shipId: string;
orderId?: string;
}, context: BRC): Promise<string | null | undefined>; }, context: BRC): Promise<string | null | undefined>;

View File

@ -8,30 +8,8 @@ export async function getMpShipState(params, context) {
const application = context.getApplication(); const application = context.getApplication();
const { type, } = application; const { type, } = application;
if (type === 'wechatMp') { if (type === 'wechatMp') {
const { depositId, orderId } = params; const { shipId } = params;
if (depositId) { const shipState = await getShipState(context, shipId);
//充值 return shipState;
const deposits = await context.select('deposit', {
data: {
id: 1,
iState: 1,
shipId: 1,
ship: {
id: 1,
iState: 1,
},
},
filter: {
id: depositId,
}
}, {});
const deposit = deposits[0];
if (deposit) {
const shipState = await getShipState(context, deposit);
return shipState;
}
}
if (orderId) {
}
} }
} }

View File

@ -24,10 +24,10 @@ export default OakComponent({
}, },
isList: false, isList: false,
properties: { properties: {
accountId: '', // 是否可以使用帐户中的余额抵扣 accountId: '',
accountAvailMax: 0, // 本次交易可以使用的帐户中的Avail max值调用者自己保证此数值的一致性不要扣成负数 accountAvailMax: 0,
onSetPays: (pays) => undefined, onSetPays: (pays) => undefined,
accountTips: '', // 使用余额支付的提示说明 accountTips: '',
autoStartPay: false, autoStartPay: false,
}, },
formData({ data }) { formData({ data }) {

View File

@ -369,7 +369,7 @@ export default OakComponent({
async success() { async success() {
console.log('success'); console.log('success');
const mpShipState = await this.features.cache.exec('getMpShipState', { const mpShipState = await this.features.cache.exec('getMpShipState', {
depositId, shipId: next.shipId,
}); });
if (mpShipState === 'received') { if (mpShipState === 'received') {
this.setMessage({ this.setMessage({

View File

@ -59,7 +59,7 @@ const triggers = [
} }
}, },
{ {
// todo应该是所有的ship // tododeposit的ship和 shipClazz中有配置wechatShip且可获取openId的order的ship
entity: 'ship', entity: 'ship',
name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口', name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口',
action: 'ship', action: 'ship',
@ -77,21 +77,45 @@ const triggers = [
data: { data: {
id: 1, id: 1,
}, },
indexFrom: 0, },
count: 1, entity: 1,
entityId: 1,
shipServiceId: 1,
shipOrder$ship: {
$entity: 'shipOrder',
data: {
id: 1,
orderId: 1,
}
},
wechatMpShip: {
id: 1,
applicationId: 1,
} }
}, },
filter, filter,
}, {}); }, {});
const { type, deposit$ship: deposits } = ship || {}; const { id: shipId, type, deposit$ship: deposits, shipOrder$ship, shipServiceId, entity, entityId, wechatMpShip } = ship || {};
if (type === 'virtual') { if (deposits && deposits.length > 0) {
const deposit = deposits?.[0]; //充值 (此时该充值必定为受发货限制的小程序上的充值)
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
//发货前先查询,检查是否为未同步微信端发货状态 //发货前先查询,检查是否为未同步微信端发货状态
const shipState = await getShipState(context, deposit); const shipState = await getShipState(context, shipId);
if (shipState === 'unshipped') { if (shipState === 'unshipped') {
const result = await uploadShippingInfo({ depositId: deposit?.id, }, context); await uploadShippingInfo(shipId, context);
if (result) { return 1;
}
}
if (shipOrder$ship && shipOrder$ship.length > 0) {
//订单
const clazz = await getShipClazz(entity, entityId, context);
const { openId } = await clazz.getReceiverInfo(shipOrder$ship.map((ele) => ele.orderId), wechatMpShip?.applicationId, context);
if (openId) {
//当存在openId时调用小程序发货信息录入
const shipState = await getShipState(context, shipId);
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
if (shipState && ['unshipped', 'shipping'].includes(shipState)) {
// 发货/更换发货
await uploadShippingInfo(shipId, context);
return 1; return 1;
} }
} }
@ -203,7 +227,7 @@ const triggers = [
if (packaged.length > 0) { if (packaged.length > 0) {
await context.operate('order', { await context.operate('order', {
id: await generateNewIdAsync(), id: await generateNewIdAsync(),
action: 'ship', action: 'send',
data: {}, data: {},
filter: { filter: {
id: { id: {
@ -249,33 +273,16 @@ const triggers = [
} }
} }
}, { dontCollect: true }); }, { dontCollect: true });
const packaged = orders.filter(ele => ele.receivingMethod === 'express'); await context.operate('order', {
if (packaged.length > 0) { id: await generateNewIdAsync(),
await context.operate('order', { action: 'unship',
id: await generateNewIdAsync(), data: {},
action: 'turnBack', filter: {
data: {}, id: {
filter: { $in: orders.map(ele => ele.id),
id: {
$in: packaged.map(ele => ele.id),
},
}, },
}, option); },
} }, option);
const staged = orders.filter(ele => ele.receivingMethod === 'pickup');
if (staged.length > 0) {
await context.operate('order', {
id: await generateNewIdAsync(),
action: 'cancelTaking',
data: {},
filter: {
id: {
$in: staged.map(ele => ele.id),
},
},
}, option);
}
assert(staged.length + packaged.length === orders.length);
return orders.length; return orders.length;
} }
}, },

View File

@ -2,6 +2,10 @@ import { EntityDict } from '../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import BackendRuntimeContext from '../context/BackendRuntimeContext'; import BackendRuntimeContext from '../context/BackendRuntimeContext';
export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> { export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> {
getReceiverInfo(orderIds: string[], applicationId: string, context: Context): Promise<{
openId?: string;
appWxId?: string;
}>;
available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>; available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>;
eOrder(shipId: string, context: Context): Promise<string>; eOrder(shipId: string, context: Context): Promise<string>;
cancelOrder(shipId: string, context: Context): Promise<void>; cancelOrder(shipId: string, context: Context): Promise<void>;

14
es/utils/ship.d.ts vendored
View File

@ -1,16 +1,18 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { BRC } from '../types/RuntimeCxt'; import { BRC } from '../types/RuntimeCxt';
export declare const fullShipProjection: EntityDict['ship']['Projection']; export declare const shipProjection: EntityDict['ship']['Projection'];
/**
* (130****0000)
* @param phone
*/
export declare function maskPhone(phone: string): string;
/** /**
* *
* @param shipInfo * @param shipInfo
* @param context * @param context
* @returns * @returns
*/ */
export declare function uploadShippingInfo(param: { export declare function uploadShippingInfo(shipId: string, context: BRC): Promise<void>;
depositId?: string;
orderId?: string;
}, context: BRC): Promise<true | undefined>;
/** /**
* *
* @param context * @param context
@ -18,7 +20,7 @@ export declare function uploadShippingInfo(param: {
* @param order * @param order
* @returns * @returns
*/ */
export declare function getShipState(context: BRC, deposit?: EntityDict['deposit']['Schema'], order?: EntityDict['order']['Schema']): Promise<string | undefined>; export declare function getShipState(context: BRC, shipId: string): Promise<string | undefined>;
/**shippingship /**shippingship
* @param ship * @param ship
* @param context * @param context

View File

@ -3,10 +3,14 @@ import { assert } from 'oak-domain/lib/utils/assert';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { UploadShipException } from '../types/Exception'; import { UploadShipException } from '../types/Exception';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export const fullShipProjection = { import { isMobile } from 'oak-domain/lib/utils/validator';
export const shipProjection = {
id: 1, id: 1,
type: 1, type: 1,
iState: 1, iState: 1,
entity: 1,
entityId: 1,
extraShipId: 1,
shipService: { shipService: {
id: 1, id: 1,
name: 1, name: 1,
@ -17,7 +21,6 @@ export const fullShipProjection = {
wechatMpName: 1, wechatMpName: 1,
} }
}, },
serial: 1,
deposit$ship: { deposit$ship: {
$entity: 'deposit', $entity: 'deposit',
data: { data: {
@ -39,6 +42,9 @@ export const fullShipProjection = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
@ -66,75 +72,90 @@ export const fullShipProjection = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
} }
} },
from: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
to: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
}; };
/**
* 手机号掩码(130****0000)
* @param phone
*/
export function maskPhone(phone) {
assert(isMobile(phone));
const start = phone.slice(3);
const end = phone.slice(-4);
return start + '****' + end;
}
/** /**
* 小程序发货信息录入 * 小程序发货信息录入
* @param shipInfo * @param shipInfo
* @param context * @param context
* @returns * @returns
*/ */
export async function uploadShippingInfo(param, context) { export async function uploadShippingInfo(shipId, context) {
const { depositId, orderId } = param; const [ship] = await context.select('ship', {
if (depositId) { data: shipProjection,
const [deposit] = await context.select('deposit', { filter: {
data: { id: shipId,
id: 1, }
iState: 1, }, { forUpdate: true });
shipId: 1, const { deposit$ship: deposits, shipOrder$ship, type: shipType, extraShipId, shipService, from, to } = ship;
ship: { // assert(iState === 'unshipped'); //可更改一次发货信息
id: 1, //发货信息录入前检查小程序订单发货状态
iState: 1, const shipState = await getShipState(context, shipId);
type: 1, const now = dayjs().format();
}, const application = context.getApplication();
pay$deposit: { const { type, config } = application;
$entity: 'pay', assert(type === 'wechatMp');
data: { const { appId, appSecret } = config;
id: 1, const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
entity: 1, if (deposits && deposits.length > 0 && shipState === 'unshipped') {
entityId: 1, //充值
meta: 1, assert(deposits.length === 1);
applicationId: 1, const [deposit] = deposits;
}, const { pay$deposit: pays, } = deposit;
filter: {
iState: 'paid',
},
indexFrom: 0,
count: 1,
},
},
filter: {
id: depositId,
}
}, {
blockTrigger: true,
forUpdate: true,
});
const { pay$deposit: pays, } = deposit || {};
const pay = pays?.[0]; const pay = pays?.[0];
const applicationId = pay?.applicationId;
assert(applicationId);
const [application] = await context.select('application', {
data: {
id: 1,
type: 1,
config: 1.
},
filter: {
id: applicationId,
}
}, {
blockTrigger: true,
forUpdate: true,
});
const { type, config } = application;
assert(type === 'wechatMp');
const { appId, appSecret } = config;
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
const meta = pay?.meta; const meta = pay?.meta;
const shipInfo = { const shipInfo = {
order_key: { order_key: {
@ -148,22 +169,64 @@ export async function uploadShippingInfo(param, context) {
item_desc: '账户充值', item_desc: '账户充值',
} }
], ],
upload_time: dayjs().format(), upload_time: now,
payer: { payer: {
openid: meta?.payer?.openid, openid: meta?.payer?.openid,
}, },
}; };
const result = await wechatInstance.uploadShippingInfo(shipInfo); const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode === 0) { if (result?.errcode !== 0) {
return true;
}
else {
console.error(JSON.stringify(result)); console.error(JSON.stringify(result));
throw new UploadShipException(result?.message); throw new UploadShipException(result?.message);
} }
} }
if (orderId) { else if (shipOrder$ship && shipOrder$ship.length > 0 && shipState && ['unshipped', 'shipping'].includes(shipState)) {
//订单 //订单 每笔微信支付调用一次接口
const orders = shipOrder$ship.map((ele) => ele.order);
const fromPhoneStr = maskPhone(from.phone);
const toPhoneStr = maskPhone(to.phone);
for (const order of orders) {
const { pay$order, desc } = order;
const wechatPay = pay$order.find((ele) => ele.entity === 'wpProduct');
const meta = wechatPay?.meta;
let shippingList = [];
if (shipType === 'express') {
shippingList = [
{
tracking_no: extraShipId,
express_company: shipService?.shipCompany?.wechatMpName,
item_desc: desc,
contact: {
consignor_contact: fromPhoneStr,
receiver_contact: toPhoneStr,
}
}
];
}
else if (shipType === 'pickup') {
shippingList = [{
item_desc: desc,
}];
}
const shipInfo = {
order_key: {
order_number_type: 2,
transaction_id: meta?.transaction_id,
},
logistic_type: shipType === 'express' ? 1 : 4,
delivery_mode: 1,
shipping_list: shippingList,
upload_time: now,
payer: {
openid: meta?.payer?.openid,
},
};
const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode !== 0) {
console.error(JSON.stringify(result));
throw new UploadShipException(result?.message);
}
}
} }
} }
/** /**
@ -173,89 +236,67 @@ export async function uploadShippingInfo(param, context) {
* @param order * @param order
* @returns * @returns
*/ */
export async function getShipState(context, deposit, order) { export async function getShipState(context, shipId) {
const [ship] = await context.select('ship', {
data: shipProjection,
filter: {
id: shipId,
}
}, {
blockTrigger: true,
forUpdate: true
});
assert(ship);
const { deposit$ship: deposits, shipOrder$ship } = ship;
const application = context.getApplication(); const application = context.getApplication();
const { type, config } = application; const { type, config } = application;
assert(type === 'wechatMp'); assert(type === 'wechatMp');
const { appId, appSecret } = config; const { appId, appSecret } = config;
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret); const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
if (deposit) { let info = undefined;
if (deposits && deposits.length > 0) {
//充值 //充值
let deposit2 = deposit; const [deposit] = deposits;
const deposits = await context.select('deposit', { const { pay$deposit: pays, } = deposit;
data: {
id: 1,
iState: 1,
pay$deposit: {
$entity: 'pay',
data: {
id: 1,
iState: 1,
meta: 1,
entity: 1,
entityId: 1,
},
filter: {
iState: 'paid',
},
indexFrom: 0,
count: 1,
},
shipId: 1,
ship: {
id: 1,
type: 1,
iState: 1,
}
},
filter: {
id: deposit?.id,
}
}, {
blockTrigger: true,
forUpdate: true,
});
deposit2 = deposits[0];
const { pay$deposit: pays, ship, shipId } = deposit2;
const pay = pays?.[0]; const pay = pays?.[0];
if (shipId && pay) { if (shipId && pay) {
const info = { info = {
transaction_id: pay?.meta?.transaction_id transaction_id: pay?.meta?.transaction_id
}; };
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
// if (action && ship?.iState !== state) {
// await context.operate('ship', {
// id: await generateNewIdAsync(),
// action,
// data: {},
// filter: {
// id: shipId,
// }
// }, {});
// }
return state;
}
} }
} }
if (order) { else if (shipOrder$ship && shipOrder$ship.length > 0) {
const order = shipOrder$ship[0].order;
const { pay$order: pays, } = order;
const pay = pays?.find((ele) => ele.entity === 'wpProduct');
if (shipId && pay) {
info = {
transaction_id: pay?.meta?.transaction_id
};
}
}
if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
return state;
}
} }
} }
/**shippingship /**shippingship
@ -265,9 +306,9 @@ export async function getShipState(context, deposit, order) {
*/ */
export async function refreshtShipState(ship, context) { export async function refreshtShipState(ship, context) {
let ship2 = ship; let ship2 = ship;
if (!ship2.iState || (!ship2.deposit$ship && !ship2.shipOrder$ship)) { if (!ship2.iState) {
const ships = await context.select('ship', { const ships = await context.select('ship', {
data: fullShipProjection, data: shipProjection,
filter: { filter: {
id: ship.id, id: ship.id,
} }
@ -277,57 +318,26 @@ export async function refreshtShipState(ship, context) {
}); });
ship2 = ships[0]; ship2 = ships[0];
} }
let application = undefined, info = undefined; const state = await getShipState(context, ship2.id);
const { deposit$ship: deposits, shipOrder$ship: shipOrders, } = ship2; let action = undefined;
if (deposits && deposits.length > 0) { switch (state) {
//充值 case 'shipping': //已发货
const deposit = deposits[0]; action = 'ship';
const { pay$deposit } = deposit; break;
application = pay$deposit?.[0].application; case 'received': //确认收货
const meta = pay$deposit?.[0].meta; action = 'receive';
info = { break;
transaction_id: meta?.transaction_id default:
}; action = undefined;
} }
else if (shipOrders && shipOrders.length > 0) { if (action && ship2.iState !== state) {
//订单 return await context.operate('ship', {
} id: await generateNewIdAsync(),
if (application) { action,
const { type, config } = application; data: {},
assert(type === 'wechatMp'); filter: {
const { appId, appSecret } = config; id: ship2.id,
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
action = 'ship';
break;
case 3: //确认收货
state = 'received';
action = 'receive';
break;
}
if (action && ship2.iState !== state) {
return await context.operate('ship', {
id: await generateNewIdAsync(),
action,
data: {},
filter: {
id: ship2.id,
}
}, {});
}
} }
} }, {});
} }
} }

View File

@ -8,7 +8,10 @@ type ExtraAddExpressOrderData = Omit<AddExpressOrderData, 'order_id' | 'openid'
export default class WechatMpShipDebug<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> implements ShipClazz<ED, Context> { export default class WechatMpShipDebug<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> implements ShipClazz<ED, Context> {
private wechatMpShipId; private wechatMpShipId;
private wechatMpShip?; private wechatMpShip?;
private getReceiverInfo; getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{
openId?: string;
appWxId?: string;
}>;
private getExtraData; private getExtraData;
constructor(wechatMpShipId: string, getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{ constructor(wechatMpShipId: string, getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{
openId?: string; openId?: string;

View File

@ -18,6 +18,7 @@ const ShipServiceCodeDict = {
export default class WechatMpShipDebug { export default class WechatMpShipDebug {
wechatMpShipId; wechatMpShipId;
wechatMpShip; wechatMpShip;
// 这个外部可能也需要调用
getReceiverInfo; getReceiverInfo;
getExtraData; getExtraData;
constructor(wechatMpShipId, getReceiverInfo, getExtraData) { constructor(wechatMpShipId, getReceiverInfo, getExtraData) {

View File

@ -1,5 +1,5 @@
import { mergeOperationResult } from 'oak-domain/lib/utils/operationResult'; import { mergeOperationResult } from 'oak-domain/lib/utils/operationResult';
import { fullShipProjection, refreshtShipState } from '../utils/ship'; import { shipProjection, refreshtShipState } from '../utils/ship';
const QUERY_PAYING_STATE_GAP = process.env.NODE_ENV === 'production' ? 600 * 1000 : 60 * 1000; const QUERY_PAYING_STATE_GAP = process.env.NODE_ENV === 'production' ? 600 * 1000 : 60 * 1000;
const watchers = [ const watchers = [
{ {
@ -14,7 +14,7 @@ const watchers = [
}, },
}; };
}, },
projection: fullShipProjection, projection: shipProjection,
fn: async (context, data) => { fn: async (context, data) => {
const results = []; const results = [];
for (const ship of data) { for (const ship of data) {

View File

@ -7,7 +7,6 @@ export type AspectDict<ED extends EntityDict> = {
withdrawAccountId?: string; withdrawAccountId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>;
getMpShipState: (params: { getMpShipState: (params: {
depositId?: string; shipId: string;
orderId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>;
}; };

View File

@ -5,6 +5,5 @@ import { BRC } from '../types/RuntimeCxt';
* @param context * @param context
*/ */
export declare function getMpShipState(params: { export declare function getMpShipState(params: {
depositId?: string; shipId: string;
orderId?: string;
}, context: BRC): Promise<string | null | undefined>; }, context: BRC): Promise<string | null | undefined>;

View File

@ -11,31 +11,9 @@ async function getMpShipState(params, context) {
const application = context.getApplication(); const application = context.getApplication();
const { type, } = application; const { type, } = application;
if (type === 'wechatMp') { if (type === 'wechatMp') {
const { depositId, orderId } = params; const { shipId } = params;
if (depositId) { const shipState = await (0, ship_1.getShipState)(context, shipId);
//充值 return shipState;
const deposits = await context.select('deposit', {
data: {
id: 1,
iState: 1,
shipId: 1,
ship: {
id: 1,
iState: 1,
},
},
filter: {
id: depositId,
}
}, {});
const deposit = deposits[0];
if (deposit) {
const shipState = await (0, ship_1.getShipState)(context, deposit);
return shipState;
}
}
if (orderId) {
}
} }
} }
exports.getMpShipState = getMpShipState; exports.getMpShipState = getMpShipState;

View File

@ -62,7 +62,7 @@ const triggers = [
} }
}, },
{ {
// todo应该是所有的ship // tododeposit的ship和 shipClazz中有配置wechatShip且可获取openId的order的ship
entity: 'ship', entity: 'ship',
name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口', name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口',
action: 'ship', action: 'ship',
@ -80,21 +80,45 @@ const triggers = [
data: { data: {
id: 1, id: 1,
}, },
indexFrom: 0, },
count: 1, entity: 1,
entityId: 1,
shipServiceId: 1,
shipOrder$ship: {
$entity: 'shipOrder',
data: {
id: 1,
orderId: 1,
}
},
wechatMpShip: {
id: 1,
applicationId: 1,
} }
}, },
filter, filter,
}, {}); }, {});
const { type, deposit$ship: deposits } = ship || {}; const { id: shipId, type, deposit$ship: deposits, shipOrder$ship, shipServiceId, entity, entityId, wechatMpShip } = ship || {};
if (type === 'virtual') { if (deposits && deposits.length > 0) {
const deposit = deposits?.[0]; //充值 (此时该充值必定为受发货限制的小程序上的充值)
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
//发货前先查询,检查是否为未同步微信端发货状态 //发货前先查询,检查是否为未同步微信端发货状态
const shipState = await (0, ship_1.getShipState)(context, deposit); const shipState = await (0, ship_1.getShipState)(context, shipId);
if (shipState === 'unshipped') { if (shipState === 'unshipped') {
const result = await (0, ship_1.uploadShippingInfo)({ depositId: deposit?.id, }, context); await (0, ship_1.uploadShippingInfo)(shipId, context);
if (result) { return 1;
}
}
if (shipOrder$ship && shipOrder$ship.length > 0) {
//订单
const clazz = await (0, shipClazz_1.getShipClazz)(entity, entityId, context);
const { openId } = await clazz.getReceiverInfo(shipOrder$ship.map((ele) => ele.orderId), wechatMpShip?.applicationId, context);
if (openId) {
//当存在openId时调用小程序发货信息录入
const shipState = await (0, ship_1.getShipState)(context, shipId);
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
if (shipState && ['unshipped', 'shipping'].includes(shipState)) {
// 发货/更换发货
await (0, ship_1.uploadShippingInfo)(shipId, context);
return 1; return 1;
} }
} }
@ -206,7 +230,7 @@ const triggers = [
if (packaged.length > 0) { if (packaged.length > 0) {
await context.operate('order', { await context.operate('order', {
id: await (0, uuid_1.generateNewIdAsync)(), id: await (0, uuid_1.generateNewIdAsync)(),
action: 'ship', action: 'send',
data: {}, data: {},
filter: { filter: {
id: { id: {
@ -252,33 +276,16 @@ const triggers = [
} }
} }
}, { dontCollect: true }); }, { dontCollect: true });
const packaged = orders.filter(ele => ele.receivingMethod === 'express'); await context.operate('order', {
if (packaged.length > 0) { id: await (0, uuid_1.generateNewIdAsync)(),
await context.operate('order', { action: 'unship',
id: await (0, uuid_1.generateNewIdAsync)(), data: {},
action: 'turnBack', filter: {
data: {}, id: {
filter: { $in: orders.map(ele => ele.id),
id: {
$in: packaged.map(ele => ele.id),
},
}, },
}, option); },
} }, option);
const staged = orders.filter(ele => ele.receivingMethod === 'pickup');
if (staged.length > 0) {
await context.operate('order', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'cancelTaking',
data: {},
filter: {
id: {
$in: staged.map(ele => ele.id),
},
},
}, option);
}
(0, assert_1.default)(staged.length + packaged.length === orders.length);
return orders.length; return orders.length;
} }
}, },

View File

@ -2,6 +2,10 @@ import { EntityDict } from '../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import BackendRuntimeContext from '../context/BackendRuntimeContext'; import BackendRuntimeContext from '../context/BackendRuntimeContext';
export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> { export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> {
getReceiverInfo(orderIds: string[], applicationId: string, context: Context): Promise<{
openId?: string;
appWxId?: string;
}>;
available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>; available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>;
eOrder(shipId: string, context: Context): Promise<string>; eOrder(shipId: string, context: Context): Promise<string>;
cancelOrder(shipId: string, context: Context): Promise<void>; cancelOrder(shipId: string, context: Context): Promise<void>;

14
lib/utils/ship.d.ts vendored
View File

@ -1,16 +1,18 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { BRC } from '../types/RuntimeCxt'; import { BRC } from '../types/RuntimeCxt';
export declare const fullShipProjection: EntityDict['ship']['Projection']; export declare const shipProjection: EntityDict['ship']['Projection'];
/**
* (130****0000)
* @param phone
*/
export declare function maskPhone(phone: string): string;
/** /**
* *
* @param shipInfo * @param shipInfo
* @param context * @param context
* @returns * @returns
*/ */
export declare function uploadShippingInfo(param: { export declare function uploadShippingInfo(shipId: string, context: BRC): Promise<void>;
depositId?: string;
orderId?: string;
}, context: BRC): Promise<true | undefined>;
/** /**
* *
* @param context * @param context
@ -18,7 +20,7 @@ export declare function uploadShippingInfo(param: {
* @param order * @param order
* @returns * @returns
*/ */
export declare function getShipState(context: BRC, deposit?: EntityDict['deposit']['Schema'], order?: EntityDict['order']['Schema']): Promise<string | undefined>; export declare function getShipState(context: BRC, shipId: string): Promise<string | undefined>;
/**shippingship /**shippingship
* @param ship * @param ship
* @param context * @param context

View File

@ -1,16 +1,20 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.refreshtShipState = exports.getShipState = exports.uploadShippingInfo = exports.fullShipProjection = void 0; exports.refreshtShipState = exports.getShipState = exports.uploadShippingInfo = exports.maskPhone = exports.shipProjection = void 0;
const tslib_1 = require("tslib"); const tslib_1 = require("tslib");
const oak_external_sdk_1 = require("oak-external-sdk"); const oak_external_sdk_1 = require("oak-external-sdk");
const assert_1 = require("oak-domain/lib/utils/assert"); const assert_1 = require("oak-domain/lib/utils/assert");
const uuid_1 = require("oak-domain/lib/utils/uuid"); const uuid_1 = require("oak-domain/lib/utils/uuid");
const Exception_1 = require("../types/Exception"); const Exception_1 = require("../types/Exception");
const dayjs_1 = tslib_1.__importDefault(require("dayjs")); const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
exports.fullShipProjection = { const validator_1 = require("oak-domain/lib/utils/validator");
exports.shipProjection = {
id: 1, id: 1,
type: 1, type: 1,
iState: 1, iState: 1,
entity: 1,
entityId: 1,
extraShipId: 1,
shipService: { shipService: {
id: 1, id: 1,
name: 1, name: 1,
@ -21,7 +25,6 @@ exports.fullShipProjection = {
wechatMpName: 1, wechatMpName: 1,
} }
}, },
serial: 1,
deposit$ship: { deposit$ship: {
$entity: 'deposit', $entity: 'deposit',
data: { data: {
@ -43,6 +46,9 @@ exports.fullShipProjection = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
@ -70,75 +76,91 @@ exports.fullShipProjection = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
} }
} },
from: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
to: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
}; };
/**
* 手机号掩码(130****0000)
* @param phone
*/
function maskPhone(phone) {
(0, assert_1.assert)((0, validator_1.isMobile)(phone));
const start = phone.slice(3);
const end = phone.slice(-4);
return start + '****' + end;
}
exports.maskPhone = maskPhone;
/** /**
* 小程序发货信息录入 * 小程序发货信息录入
* @param shipInfo * @param shipInfo
* @param context * @param context
* @returns * @returns
*/ */
async function uploadShippingInfo(param, context) { async function uploadShippingInfo(shipId, context) {
const { depositId, orderId } = param; const [ship] = await context.select('ship', {
if (depositId) { data: exports.shipProjection,
const [deposit] = await context.select('deposit', { filter: {
data: { id: shipId,
id: 1, }
iState: 1, }, { forUpdate: true });
shipId: 1, const { deposit$ship: deposits, shipOrder$ship, type: shipType, extraShipId, shipService, from, to } = ship;
ship: { // assert(iState === 'unshipped'); //可更改一次发货信息
id: 1, //发货信息录入前检查小程序订单发货状态
iState: 1, const shipState = await getShipState(context, shipId);
type: 1, const now = (0, dayjs_1.default)().format();
}, const application = context.getApplication();
pay$deposit: { const { type, config } = application;
$entity: 'pay', (0, assert_1.assert)(type === 'wechatMp');
data: { const { appId, appSecret } = config;
id: 1, const wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret);
entity: 1, if (deposits && deposits.length > 0 && shipState === 'unshipped') {
entityId: 1, //充值
meta: 1, (0, assert_1.assert)(deposits.length === 1);
applicationId: 1, const [deposit] = deposits;
}, const { pay$deposit: pays, } = deposit;
filter: {
iState: 'paid',
},
indexFrom: 0,
count: 1,
},
},
filter: {
id: depositId,
}
}, {
blockTrigger: true,
forUpdate: true,
});
const { pay$deposit: pays, } = deposit || {};
const pay = pays?.[0]; const pay = pays?.[0];
const applicationId = pay?.applicationId;
(0, assert_1.assert)(applicationId);
const [application] = await context.select('application', {
data: {
id: 1,
type: 1,
config: 1.
},
filter: {
id: applicationId,
}
}, {
blockTrigger: true,
forUpdate: true,
});
const { type, config } = application;
(0, assert_1.assert)(type === 'wechatMp');
const { appId, appSecret } = config;
const wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret);
const meta = pay?.meta; const meta = pay?.meta;
const shipInfo = { const shipInfo = {
order_key: { order_key: {
@ -152,22 +174,64 @@ async function uploadShippingInfo(param, context) {
item_desc: '账户充值', item_desc: '账户充值',
} }
], ],
upload_time: (0, dayjs_1.default)().format(), upload_time: now,
payer: { payer: {
openid: meta?.payer?.openid, openid: meta?.payer?.openid,
}, },
}; };
const result = await wechatInstance.uploadShippingInfo(shipInfo); const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode === 0) { if (result?.errcode !== 0) {
return true;
}
else {
console.error(JSON.stringify(result)); console.error(JSON.stringify(result));
throw new Exception_1.UploadShipException(result?.message); throw new Exception_1.UploadShipException(result?.message);
} }
} }
if (orderId) { else if (shipOrder$ship && shipOrder$ship.length > 0 && shipState && ['unshipped', 'shipping'].includes(shipState)) {
//订单 //订单 每笔微信支付调用一次接口
const orders = shipOrder$ship.map((ele) => ele.order);
const fromPhoneStr = maskPhone(from.phone);
const toPhoneStr = maskPhone(to.phone);
for (const order of orders) {
const { pay$order, desc } = order;
const wechatPay = pay$order.find((ele) => ele.entity === 'wpProduct');
const meta = wechatPay?.meta;
let shippingList = [];
if (shipType === 'express') {
shippingList = [
{
tracking_no: extraShipId,
express_company: shipService?.shipCompany?.wechatMpName,
item_desc: desc,
contact: {
consignor_contact: fromPhoneStr,
receiver_contact: toPhoneStr,
}
}
];
}
else if (shipType === 'pickup') {
shippingList = [{
item_desc: desc,
}];
}
const shipInfo = {
order_key: {
order_number_type: 2,
transaction_id: meta?.transaction_id,
},
logistic_type: shipType === 'express' ? 1 : 4,
delivery_mode: 1,
shipping_list: shippingList,
upload_time: now,
payer: {
openid: meta?.payer?.openid,
},
};
const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode !== 0) {
console.error(JSON.stringify(result));
throw new Exception_1.UploadShipException(result?.message);
}
}
} }
} }
exports.uploadShippingInfo = uploadShippingInfo; exports.uploadShippingInfo = uploadShippingInfo;
@ -178,89 +242,67 @@ exports.uploadShippingInfo = uploadShippingInfo;
* @param order * @param order
* @returns * @returns
*/ */
async function getShipState(context, deposit, order) { async function getShipState(context, shipId) {
const [ship] = await context.select('ship', {
data: exports.shipProjection,
filter: {
id: shipId,
}
}, {
blockTrigger: true,
forUpdate: true
});
(0, assert_1.assert)(ship);
const { deposit$ship: deposits, shipOrder$ship } = ship;
const application = context.getApplication(); const application = context.getApplication();
const { type, config } = application; const { type, config } = application;
(0, assert_1.assert)(type === 'wechatMp'); (0, assert_1.assert)(type === 'wechatMp');
const { appId, appSecret } = config; const { appId, appSecret } = config;
const wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret); const wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret);
if (deposit) { let info = undefined;
if (deposits && deposits.length > 0) {
//充值 //充值
let deposit2 = deposit; const [deposit] = deposits;
const deposits = await context.select('deposit', { const { pay$deposit: pays, } = deposit;
data: {
id: 1,
iState: 1,
pay$deposit: {
$entity: 'pay',
data: {
id: 1,
iState: 1,
meta: 1,
entity: 1,
entityId: 1,
},
filter: {
iState: 'paid',
},
indexFrom: 0,
count: 1,
},
shipId: 1,
ship: {
id: 1,
type: 1,
iState: 1,
}
},
filter: {
id: deposit?.id,
}
}, {
blockTrigger: true,
forUpdate: true,
});
deposit2 = deposits[0];
const { pay$deposit: pays, ship, shipId } = deposit2;
const pay = pays?.[0]; const pay = pays?.[0];
if (shipId && pay) { if (shipId && pay) {
const info = { info = {
transaction_id: pay?.meta?.transaction_id transaction_id: pay?.meta?.transaction_id
}; };
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
// if (action && ship?.iState !== state) {
// await context.operate('ship', {
// id: await generateNewIdAsync(),
// action,
// data: {},
// filter: {
// id: shipId,
// }
// }, {});
// }
return state;
}
} }
} }
if (order) { else if (shipOrder$ship && shipOrder$ship.length > 0) {
const order = shipOrder$ship[0].order;
const { pay$order: pays, } = order;
const pay = pays?.find((ele) => ele.entity === 'wpProduct');
if (shipId && pay) {
info = {
transaction_id: pay?.meta?.transaction_id
};
}
}
if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
return state;
}
} }
} }
exports.getShipState = getShipState; exports.getShipState = getShipState;
@ -271,9 +313,9 @@ exports.getShipState = getShipState;
*/ */
async function refreshtShipState(ship, context) { async function refreshtShipState(ship, context) {
let ship2 = ship; let ship2 = ship;
if (!ship2.iState || (!ship2.deposit$ship && !ship2.shipOrder$ship)) { if (!ship2.iState) {
const ships = await context.select('ship', { const ships = await context.select('ship', {
data: exports.fullShipProjection, data: exports.shipProjection,
filter: { filter: {
id: ship.id, id: ship.id,
} }
@ -283,58 +325,27 @@ async function refreshtShipState(ship, context) {
}); });
ship2 = ships[0]; ship2 = ships[0];
} }
let application = undefined, info = undefined; const state = await getShipState(context, ship2.id);
const { deposit$ship: deposits, shipOrder$ship: shipOrders, } = ship2; let action = undefined;
if (deposits && deposits.length > 0) { switch (state) {
//充值 case 'shipping': //已发货
const deposit = deposits[0]; action = 'ship';
const { pay$deposit } = deposit; break;
application = pay$deposit?.[0].application; case 'received': //确认收货
const meta = pay$deposit?.[0].meta; action = 'receive';
info = { break;
transaction_id: meta?.transaction_id default:
}; action = undefined;
} }
else if (shipOrders && shipOrders.length > 0) { if (action && ship2.iState !== state) {
//订单 return await context.operate('ship', {
} id: await (0, uuid_1.generateNewIdAsync)(),
if (application) { action,
const { type, config } = application; data: {},
(0, assert_1.assert)(type === 'wechatMp'); filter: {
const { appId, appSecret } = config; id: ship2.id,
const wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret);
if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
action = 'ship';
break;
case 3: //确认收货
state = 'received';
action = 'receive';
break;
}
if (action && ship2.iState !== state) {
return await context.operate('ship', {
id: await (0, uuid_1.generateNewIdAsync)(),
action,
data: {},
filter: {
id: ship2.id,
}
}, {});
}
} }
} }, {});
} }
} }
exports.refreshtShipState = refreshtShipState; exports.refreshtShipState = refreshtShipState;

View File

@ -8,7 +8,10 @@ type ExtraAddExpressOrderData = Omit<AddExpressOrderData, 'order_id' | 'openid'
export default class WechatMpShipDebug<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> implements ShipClazz<ED, Context> { export default class WechatMpShipDebug<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> implements ShipClazz<ED, Context> {
private wechatMpShipId; private wechatMpShipId;
private wechatMpShip?; private wechatMpShip?;
private getReceiverInfo; getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{
openId?: string;
appWxId?: string;
}>;
private getExtraData; private getExtraData;
constructor(wechatMpShipId: string, getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{ constructor(wechatMpShipId: string, getReceiverInfo: (orderIds: string[], applicationId: string, context: Context) => Promise<{
openId?: string; openId?: string;

View File

@ -21,6 +21,7 @@ const ShipServiceCodeDict = {
class WechatMpShipDebug { class WechatMpShipDebug {
wechatMpShipId; wechatMpShipId;
wechatMpShip; wechatMpShip;
// 这个外部可能也需要调用
getReceiverInfo; getReceiverInfo;
getExtraData; getExtraData;
constructor(wechatMpShipId, getReceiverInfo, getExtraData) { constructor(wechatMpShipId, getReceiverInfo, getExtraData) {

View File

@ -16,7 +16,7 @@ const watchers = [
}, },
}; };
}, },
projection: ship_1.fullShipProjection, projection: ship_1.shipProjection,
fn: async (context, data) => { fn: async (context, data) => {
const results = []; const results = [];
for (const ship of data) { for (const ship of data) {

View File

@ -8,7 +8,6 @@ export type AspectDict<ED extends EntityDict> = {
withdrawAccountId?: string; withdrawAccountId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['withdraw']['CreateSingle']['data']>;
getMpShipState: (params: { getMpShipState: (params: {
depositId?: string; shipId: string;
orderId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>; }, context: BackendRuntimeContext<ED>) => Promise<EntityDict['ship']['Schema']['iState']>;
}; };

View File

@ -11,41 +11,18 @@ import { getShipState } from '../utils/ship';
*/ */
export async function getMpShipState( export async function getMpShipState(
params: { params: {
depositId?: string, shipId: string,
orderId?: string,
}, },
context: BRC context: BRC
) { ) {
const application = context.getApplication(); const application = context.getApplication();
const { type, } = application!; const { type, } = application!;
if (type === 'wechatMp') { if (type === 'wechatMp') {
const { depositId, orderId } = params; const { shipId } = params;
if (depositId) { const shipState = await getShipState(
//充值 context,
const deposits = await context.select('deposit', { shipId,
data: { ) as EntityDict['ship']['Schema']['iState'];
id: 1, return shipState;
iState: 1,
shipId: 1,
ship: {
id: 1,
iState: 1,
},
},
filter: {
id: depositId,
}
}, {});
const deposit = deposits[0] as EntityDict['deposit']['Schema'];
if (deposit) {
const shipState = await getShipState(
context,
deposit,
) as EntityDict['ship']['Schema']['iState'];
return shipState;
}
}
if (orderId) {
}
} }
} }

View File

@ -162,7 +162,7 @@ export default OakComponent({
); );
const { mode } = this.props; const { mode } = this.props;
const succeedable =data["#oakLegalActions"]?.includes('succeedPaying'); const succeedable = data["#oakLegalActions"]?.includes('succeedPaying');
const payChannels = this.features.pay.getPayChannels(); const payChannels = this.features.pay.getPayChannels();
const offlines = this.features.cache.get('offlineAccount', { const offlines = this.features.cache.get('offlineAccount', {
@ -400,7 +400,7 @@ export default OakComponent({
async success() { async success() {
console.log('success'); console.log('success');
const mpShipState = await this.features.cache.exec('getMpShipState', { const mpShipState = await this.features.cache.exec('getMpShipState', {
depositId, shipId: next.shipId,
}) })
if (mpShipState === 'received') { if (mpShipState === 'received') {
this.setMessage({ this.setMessage({

View File

@ -67,7 +67,7 @@ const triggers: Trigger<EntityDict, 'ship', BRC>[] = [
} }
} as CreateTriggerCrossTxn<EntityDict, 'ship', BRC>, } as CreateTriggerCrossTxn<EntityDict, 'ship', BRC>,
{ {
// todo应该是所有的ship // tododeposit的ship和 shipClazz中有配置wechatShip且可获取openId的order的ship
entity: 'ship', entity: 'ship',
name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口', name: '当虚拟的ship变为shipping状态时调用小程序发货信息录入接口',
action: 'ship', action: 'ship',
@ -85,21 +85,47 @@ const triggers: Trigger<EntityDict, 'ship', BRC>[] = [
data: { data: {
id: 1, id: 1,
}, },
indexFrom: 0, },
count: 1, entity: 1,
entityId: 1,
shipServiceId: 1,
shipOrder$ship: {
$entity: 'shipOrder',
data: {
id: 1,
orderId: 1,
}
},
wechatMpShip: {
id: 1,
applicationId: 1,
} }
}, },
filter, filter,
}, {}); }, {});
const { type, deposit$ship: deposits } = ship || {}; const { id: shipId, type, deposit$ship: deposits, shipOrder$ship, shipServiceId, entity, entityId, wechatMpShip } = ship || {};
if (type === 'virtual') { if (deposits && deposits.length > 0) {
const deposit = deposits?.[0]; //充值 (此时该充值必定为受发货限制的小程序上的充值)
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
//发货前先查询,检查是否为未同步微信端发货状态 //发货前先查询,检查是否为未同步微信端发货状态
const shipState = await getShipState(context, deposit); const shipState = await getShipState(context, shipId!);
if (shipState === 'unshipped') { if (shipState === 'unshipped') {
const result = await uploadShippingInfo({ depositId: deposit?.id, }, context); await uploadShippingInfo(shipId!, context);
if (result) { return 1;
}
}
if (shipOrder$ship && shipOrder$ship.length > 0) {
//订单
const clazz = await getShipClazz(entity!, entityId!, context);
const { openId } = await clazz.getReceiverInfo(shipOrder$ship.map((ele) => ele.orderId!), wechatMpShip?.applicationId!, context);
if (openId) {
//当存在openId时调用小程序发货信息录入
const shipState = await getShipState(context, shipId!);
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
if (shipState && ['unshipped', 'shipping'].includes(shipState)) {
// 发货/更换发货
await uploadShippingInfo(shipId!, context);
return 1; return 1;
} }
} }

View File

@ -3,6 +3,10 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import BackendRuntimeContext from '../context/BackendRuntimeContext'; import BackendRuntimeContext from '../context/BackendRuntimeContext';
export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> { export default interface ShipClazz<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> {
getReceiverInfo(orderIds: string[], applicationId: string, context: Context): Promise<{
openId?: string;
appWxId?: string;
}>;
// 是否可以使用这个接口下单 // 是否可以使用这个接口下单
available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>; available(shipServiceId: string, orderIds: string[], context: Context): Promise<boolean>;

View File

@ -6,12 +6,18 @@ import { WechatMpConfig } from '@project/oak-app-domain/Application/Schema';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { UploadShipException } from '../types/Exception'; import { UploadShipException } from '../types/Exception';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { isMobile } from 'oak-domain/lib/utils/validator';
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { unionBy } from 'oak-domain/lib/utils/lodash';
export const fullShipProjection: EntityDict['ship']['Projection'] = { export const shipProjection: EntityDict['ship']['Projection'] = {
id: 1, id: 1,
type: 1, type: 1,
iState: 1, iState: 1,
entity: 1,
entityId: 1,
extraShipId: 1,
shipService: { shipService: {
id: 1, id: 1,
name: 1, name: 1,
@ -43,6 +49,9 @@ export const fullShipProjection: EntityDict['ship']['Projection'] = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
@ -70,11 +79,61 @@ export const fullShipProjection: EntityDict['ship']['Projection'] = {
iState: 1, iState: 1,
entity: 1, entity: 1,
entityId: 1, entityId: 1,
},
filter: {
iState: 'paid'
} }
} }
} }
} }
} },
from: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
to: {
id: 1,
detail: 1,
name: 1,
phone: 1,
area: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
parent: {
id: 1,
name: 1,
},
},
},
},
}
/**
* (130****0000)
* @param phone
*/
export function maskPhone(phone: string) {
assert(isMobile(phone));
const start = phone.slice(3);
const end = phone.slice(-4);
return start + '****' + end;
} }
/** /**
@ -84,74 +143,38 @@ export const fullShipProjection: EntityDict['ship']['Projection'] = {
* @returns * @returns
*/ */
export async function uploadShippingInfo( export async function uploadShippingInfo(
param: { shipId: string,
depositId?: string,
orderId?: string,
},
context: BRC, context: BRC,
) { ) {
const { depositId, orderId } = param; const [ship] = await context.select('ship', {
if (depositId) { data: shipProjection,
const [deposit] = await context.select('deposit', { filter: {
data: { id: shipId,
id: 1, }
iState: 1, }, { forUpdate: true });
shipId: 1, const { deposit$ship: deposits, shipOrder$ship, type: shipType, extraShipId, shipService, from, to } = ship;
ship: { // assert(iState === 'unshipped'); //可更改一次发货信息
id: 1,
iState: 1, //发货信息录入前检查小程序订单发货状态
type: 1, const shipState = await getShipState(context, shipId);
}, const now = dayjs().format();
pay$deposit: {
$entity: 'pay', const application = context.getApplication();
data: { const { type, config } = application!;
id: 1, assert(type === 'wechatMp');
entity: 1, const { appId, appSecret } = config as WechatMpConfig;
entityId: 1, const wechatInstance = WechatSDK.getInstance(
meta: 1, appId,
applicationId: 1, 'wechatMp',
}, appSecret,
filter: { ) as WechatMpInstance;
iState: 'paid',
}, if (deposits && deposits.length > 0 && shipState === 'unshipped') {
indexFrom: 0, //充值
count: 1, assert(deposits.length === 1);
}, const [deposit] = deposits;
}, const { pay$deposit: pays, } = deposit;
filter: {
id: depositId,
}
}, {
blockTrigger: true,
forUpdate: true,
});
const { pay$deposit: pays, } = deposit || {};
const pay = pays?.[0]; const pay = pays?.[0];
const applicationId = pay?.applicationId;
assert(applicationId);
const [application] = await context.select('application', {
data: {
id: 1,
type: 1,
config: 1.
},
filter: {
id: applicationId,
}
}, {
blockTrigger: true,
forUpdate: true,
})
const { type, config } = application!;
assert(type === 'wechatMp');
const { appId, appSecret } = config as WechatMpConfig;
const wechatInstance = WechatSDK.getInstance(
appId,
'wechatMp',
appSecret,
) as WechatMpInstance;
const meta = pay?.meta as { const meta = pay?.meta as {
transaction_id: string; transaction_id: string;
payer: { payer: {
@ -168,29 +191,71 @@ export async function uploadShippingInfo(
shipping_list: [ shipping_list: [
{ {
item_desc: '账户充值', item_desc: '账户充值',
} }
], ],
upload_time: dayjs().format(), upload_time: now,
payer: { payer: {
openid: meta?.payer?.openid, openid: meta?.payer?.openid,
}, },
} }
const result = await wechatInstance.uploadShippingInfo(shipInfo); const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode === 0) { if (result?.errcode !== 0) {
return true;
} else {
console.error(JSON.stringify(result)); console.error(JSON.stringify(result));
throw new UploadShipException(result?.message); throw new UploadShipException(result?.message);
} }
} else if (shipOrder$ship && shipOrder$ship.length > 0 && shipState && ['unshipped', 'shipping'].includes(shipState)) {
//订单 每笔微信支付调用一次接口
const orders = shipOrder$ship.map((ele) => ele.order);
const fromPhoneStr = maskPhone(from!.phone!);
const toPhoneStr = maskPhone(to!.phone!);
for (const order of orders) {
const { pay$order, desc } = order;
const wechatPay = pay$order!.find((ele) => ele.entity === 'wpProduct');
const meta = wechatPay?.meta as {
transaction_id: string;
payer: {
openid: string;
};
};
let shippingList: Array<Object> = [];
if (shipType === 'express') {
shippingList = [
{
tracking_no: extraShipId,
express_company: shipService?.shipCompany?.wechatMpName,
item_desc: desc,
contact: {
consignor_contact: fromPhoneStr,
receiver_contact: toPhoneStr,
}
}
]
} else if (shipType === 'pickup') {
shippingList = [{
item_desc: desc,
}]
}
const shipInfo: any = {
order_key: {
order_number_type: 2,
transaction_id: meta?.transaction_id,
},
logistic_type: shipType === 'express' ? 1 : 4,
delivery_mode: 1,
shipping_list: shippingList,
upload_time: now,
payer: {
openid: meta?.payer?.openid,
},
}
const result = await wechatInstance.uploadShippingInfo(shipInfo);
if (result?.errcode !== 0) {
console.error(JSON.stringify(result));
throw new UploadShipException(result?.message);
}
}
} }
if (orderId) {
//订单
}
} }
/** /**
@ -202,98 +267,74 @@ export async function uploadShippingInfo(
*/ */
export async function getShipState( export async function getShipState(
context: BRC, context: BRC,
deposit?: EntityDict['deposit']['Schema'], shipId: string,
order?: EntityDict['order']['Schema'],
) { ) {
const [ship] = await context.select('ship', {
data: shipProjection,
filter: {
id: shipId,
}
}, {
blockTrigger: true,
forUpdate: true
});
assert(ship);
const { deposit$ship: deposits, shipOrder$ship } = ship;
const application = context.getApplication(); const application = context.getApplication();
const { type, config } = application!; const { type, config } = application!;
assert(type === 'wechatMp'); assert(type === 'wechatMp');
const { appId, appSecret } = config as WechatMpConfig; const { appId, appSecret } = config as WechatMpConfig;
const wechatInstance = WechatSDK.getInstance( const wechatInstance = WechatSDK.getInstance(
appId, appId,
'wechatMp', 'wechatMp',
appSecret, appSecret,
) as WechatMpInstance; ) as WechatMpInstance;
if (deposit) { let info = undefined;
if (deposits && deposits.length > 0) {
//充值 //充值
let deposit2 = deposit; const [deposit] = deposits;
const deposits = await context.select('deposit', { const { pay$deposit: pays, } = deposit;
data: {
id: 1,
iState: 1,
pay$deposit: {
$entity: 'pay',
data: {
id: 1,
iState: 1,
meta: 1,
entity: 1,
entityId: 1,
},
filter: {
iState: 'paid',
},
indexFrom: 0,
count: 1,
},
shipId: 1,
ship: {
id: 1,
type: 1,
iState: 1,
}
},
filter: {
id: deposit?.id,
}
}, {
blockTrigger: true,
forUpdate: true,
});
deposit2 = deposits[0] as typeof deposit;
const { pay$deposit: pays, ship, shipId } = deposit2;
const pay = pays?.[0]; const pay = pays?.[0];
if (shipId && pay) { if (shipId && pay) {
const info = { info = {
transaction_id: (pay?.meta as { transaction_id: string })?.transaction_id
};
}
} else if (shipOrder$ship && shipOrder$ship.length > 0) {
const order = shipOrder$ship[0].order;
const { pay$order: pays, } = order;
const pay = pays?.find((ele) => ele.entity === 'wpProduct');
if (shipId && pay) {
info = {
transaction_id: (pay?.meta as { transaction_id: string })?.transaction_id transaction_id: (pay?.meta as { transaction_id: string })?.transaction_id
}; };
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
// if (action && ship?.iState !== state) {
// await context.operate('ship', {
// id: await generateNewIdAsync(),
// action,
// data: {},
// filter: {
// id: shipId,
// }
// }, {});
// }
return state;
}
} }
} }
if (order) { if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
// let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
// action = 'ship';
break;
case 3: //确认收货
state = 'received';
// action = 'receive';
break;
}
return state;
}
} }
} }
@ -309,9 +350,9 @@ export async function refreshtShipState(
context: BRC, context: BRC,
) { ) {
let ship2 = ship; let ship2 = ship;
if (!ship2.iState || (!ship2.deposit$ship && !ship2.shipOrder$ship)) { if (!ship2.iState) {
const ships = await context.select('ship', { const ships = await context.select('ship', {
data: fullShipProjection, data: shipProjection,
filter: { filter: {
id: ship.id, id: ship.id,
} }
@ -321,62 +362,26 @@ export async function refreshtShipState(
}); });
ship2 = ships[0] as typeof ship; ship2 = ships[0] as typeof ship;
} }
let application = undefined, info = undefined; const state = await getShipState(context, ship2.id!);
const { deposit$ship: deposits, shipOrder$ship: shipOrders, } = ship2; let action = undefined;
if (deposits && deposits.length > 0) { switch (state) {
//充值 case 'shipping': //已发货
const deposit = deposits[0]; action = 'ship';
const { pay$deposit } = deposit; break;
application = pay$deposit?.[0]!.application; case 'received': //确认收货
const meta = pay$deposit?.[0]!.meta; action = 'receive';
info = { break;
transaction_id: (meta as { transaction_id: string })?.transaction_id default:
}; action = undefined;
} }
else if (shipOrders && shipOrders.length > 0) { if (action && ship2.iState !== state) {
//订单 return await context.operate('ship', {
} id: await generateNewIdAsync(),
if (application) { action,
const { type, config } = application!; data: {},
assert(type === 'wechatMp'); filter: {
const { appId, appSecret } = config as WechatMpConfig; id: ship2.id,
const wechatInstance = WechatSDK.getInstance(
appId,
'wechatMp',
appSecret,
) as WechatMpInstance;
if (info) {
const result = await wechatInstance.getOrderState(info);
const { orderState, inComplaint } = result;
if (!inComplaint) {
//未处于纠纷中
let state = undefined;
let action = undefined;
switch (orderState) {
case 1: //待发货
state = 'unshipped';
break;
case 2: //已发货
state = 'shipping';
action = 'ship';
break;
case 3: //确认收货
state = 'received';
action = 'receive'
break;
}
if (action && ship2.iState !== state) {
return await context.operate('ship', {
id: await generateNewIdAsync(),
action,
data: {},
filter: {
id: ship2.id,
}
}, {});
}
} }
} }, {});
} }
} }

View File

@ -3,7 +3,7 @@ import { EntityDict } from '../oak-app-domain';
import { BRC } from '../types/RuntimeCxt'; import { BRC } from '../types/RuntimeCxt';
import { OperationResult } from 'oak-domain/lib/types'; import { OperationResult } from 'oak-domain/lib/types';
import { mergeOperationResult } from 'oak-domain/lib/utils/operationResult'; import { mergeOperationResult } from 'oak-domain/lib/utils/operationResult';
import { fullShipProjection, refreshtShipState } from '../utils/ship'; import { shipProjection, refreshtShipState } from '../utils/ship';
const QUERY_PAYING_STATE_GAP = process.env.NODE_ENV === 'production' ? 600 * 1000 : 60 * 1000; const QUERY_PAYING_STATE_GAP = process.env.NODE_ENV === 'production' ? 600 * 1000 : 60 * 1000;
@ -20,7 +20,7 @@ const watchers: Watcher<EntityDict, 'ship', BRC>[] = [
}, },
}; };
}, },
projection: fullShipProjection, projection: shipProjection,
fn: async (context, data) => { fn: async (context, data) => {
const results = [] as OperationResult<EntityDict>[]; const results = [] as OperationResult<EntityDict>[];
for (const ship of data) { for (const ship of data) {