@tbox.cn/app-contracts-mall 0.2.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/alipay-activity/cards.d.ts +305 -0
- package/dist/assembly.d.ts +73 -7
- package/dist/chunk-KIZD6FZ2.js +43 -0
- package/dist/deployment.d.ts +32 -33
- package/dist/domain/activity.d.ts +64 -0
- package/dist/domain/coupon.d.ts +2 -0
- package/dist/domain/entitlement.d.ts +53 -0
- package/dist/domain/offer.d.ts +111 -0
- package/dist/domain/operation.d.ts +22 -0
- package/dist/domain/parking.d.ts +195 -12
- package/dist/domain/points.d.ts +3 -2
- package/dist/domain/shopping-guide.d.ts +201 -0
- package/dist/index.d.ts +15 -2
- package/dist/mall-scope-url.d.ts +13 -0
- package/dist/member/cards.d.ts +636 -213
- package/dist/parking/cards.d.ts +1271 -0
- package/dist/promotion/cards.d.ts +2529 -0
- package/dist/query-context.d.ts +47 -0
- package/dist/runtime.d.ts +12 -2
- package/dist/runtime.js +940 -86
- package/dist/scope.d.ts +42 -15
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +92 -0
- package/dist/server/mall-context.d.ts +24 -0
- package/dist/server/mall-control-plane.d.ts +10 -0
- package/dist/server/request-credentials.d.ts +22 -0
- package/dist/services/customer-service.d.ts +11 -0
- package/dist/services/mall-info.d.ts +39 -0
- package/dist/services/member.d.ts +188 -31
- package/dist/services/parking.d.ts +115 -11
- package/dist/services/promotion.d.ts +100 -4
- package/dist/services/shopping-guide.d.ts +97 -0
- package/dist/shared/cards.d.ts +42 -0
- package/dist/shopping-guide/cards.d.ts +3399 -561
- package/package.json +19 -7
- package/src/alipay-activity/cards.ts +66 -0
- package/src/assembly.ts +159 -0
- package/src/deployment.ts +44 -0
- package/src/domain/activity.ts +66 -0
- package/src/domain/coupon.ts +11 -0
- package/src/domain/discount.ts +6 -0
- package/src/domain/entitlement.ts +56 -0
- package/src/domain/offer.ts +120 -0
- package/src/domain/operation.ts +40 -0
- package/src/domain/parking.ts +207 -0
- package/src/domain/points.ts +15 -0
- package/src/domain/shopping-guide.ts +242 -0
- package/src/events/index.ts +10 -0
- package/src/guard.ts +48 -0
- package/src/index.ts +34 -0
- package/src/mall-scope-url.ts +21 -0
- package/src/member/cards.ts +209 -0
- package/src/parking/cards.ts +233 -0
- package/src/promotion/cards.ts +290 -0
- package/src/query-context.ts +81 -0
- package/src/runtime.ts +43 -0
- package/src/scope.ts +92 -0
- package/src/server/index.ts +11 -0
- package/src/server/mall-context.ts +39 -0
- package/src/server/mall-control-plane.ts +92 -0
- package/src/server/request-credentials.ts +43 -0
- package/src/service-slots.json +26 -0
- package/src/services/customer-service.ts +12 -0
- package/src/services/mall-info.ts +48 -0
- package/src/services/member.ts +386 -0
- package/src/services/parking.ts +182 -0
- package/src/services/promotion.ts +126 -0
- package/src/services/shopping-guide.ts +138 -0
- package/src/shared/cards.ts +25 -0
- package/src/shopping-guide/cards.ts +438 -0
- package/tests/assembly-validate-scope.test.ts +132 -0
- package/tests/cards.test.ts +586 -0
- package/tests/context-unification.test.ts +73 -0
- package/tests/mall-control-plane.test.ts +169 -0
- package/tests/mall-scope-url.test.ts +59 -0
- package/tests/member-status.test.ts +197 -0
- package/tests/query-context.test.ts +111 -0
- package/tests/request-credentials.test.ts +38 -0
- package/tests/resolve-primitives.test.ts +55 -0
- package/tests/service-slots.test.ts +83 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +5 -0
- package/dist/shopping-guide/ports.d.ts +0 -216
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 停车域 Port(015:三 Port 重写,breaking 0.3.0——替换旧 ParkingQueryPort/ParkingSettlementPort)。
|
|
3
|
+
* 结算写全部收口进 ParkingPaymentPort(厂商真实支付链);
|
|
4
|
+
* 旧 InMemory「calcFee/useCoupon/settlePayment」模拟结算面删除(S8)。
|
|
5
|
+
*/
|
|
6
|
+
import type { MallScope } from '../scope';
|
|
7
|
+
import type { RequestContext } from '@tbox.cn/app-contracts';
|
|
8
|
+
import type {
|
|
9
|
+
ParkingVehicle,
|
|
10
|
+
ParkingPresence,
|
|
11
|
+
ParkingBenefit,
|
|
12
|
+
ParkingLocation,
|
|
13
|
+
ParkingPaymentRecord,
|
|
14
|
+
ParkingRecordPage,
|
|
15
|
+
ParkingInfo,
|
|
16
|
+
QuoteSnapshot,
|
|
17
|
+
} from '../domain/parking';
|
|
18
|
+
|
|
19
|
+
// ===== 停车域 service slot 词汇(与 service-slots.json 同锁) =====
|
|
20
|
+
|
|
21
|
+
export const PARKING_QUERY_SERVICE = 'parking.query';
|
|
22
|
+
export const PARKING_PAYMENT_SERVICE = 'parking.payment';
|
|
23
|
+
export const PARKING_VEHICLE_SERVICE = 'parking.vehicle';
|
|
24
|
+
|
|
25
|
+
export interface ParkingQueryContext {
|
|
26
|
+
readonly scope: MallScope;
|
|
27
|
+
readonly subjectId?: string;
|
|
28
|
+
readonly requestContext: RequestContext;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ===== 1. ParkingQueryPort:查询(七合一) =====
|
|
32
|
+
|
|
33
|
+
export interface ParkingQueryPort {
|
|
34
|
+
/**
|
|
35
|
+
* 可选能力口:当前凭证是否不足以查询车辆(如已授权但非会员,缺 C 端会员令牌)。
|
|
36
|
+
* 返回 true 时调用方收口为「需先入会」,不再发出必然失败的厂商请求。
|
|
37
|
+
* 缺席 = provider 无此语义(调用方 typeof 守卫,照常查询)——厂商差异留在 provider 包。
|
|
38
|
+
*/
|
|
39
|
+
requiresMembership?(q: ParkingQueryContext): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* 可选能力口:批量入场探测(在场车 + 已停时长),仅含在场车辆。
|
|
42
|
+
* 缺席 = provider 无入场查询能力(调用方 typeof 守卫,按未入场渲染)——厂商差异留在 provider 包。
|
|
43
|
+
*/
|
|
44
|
+
probeParkedVehicles?(
|
|
45
|
+
q: ParkingQueryContext,
|
|
46
|
+
plateNos: readonly string[],
|
|
47
|
+
): Promise<readonly ParkingPresence[]>;
|
|
48
|
+
/** 计费报价(恒实时打厂商,W4;已支付态命中自动收口 PaymentOperation)。
|
|
49
|
+
* 指定车牌/vehicleRef/barcode 时返回 ≤1 条;无车辆线索("我的车"语义)扫描绑定车队时返回所有在场有费车辆(≥1 条)。空数组 = 无在场车。 */
|
|
50
|
+
queryFee(
|
|
51
|
+
q: ParkingQueryContext,
|
|
52
|
+
input: { plateNo?: string; vehicleRef?: string; barcode?: string },
|
|
53
|
+
): Promise<readonly QuoteSnapshot[]>;
|
|
54
|
+
/** 绑定车辆列表(parked 可选:provider 有入场查询能力时给出) */
|
|
55
|
+
listVehicles(q: ParkingQueryContext): Promise<{
|
|
56
|
+
bound: readonly ParkingVehicle[];
|
|
57
|
+
}>;
|
|
58
|
+
/** 缴费记录(脱敏;detail 含 entry/exit/payOrderNoMasked)。
|
|
59
|
+
* orderStatus 可选:厂商交易状态过滤串(逗号分隔,语义由 provider 定义)。
|
|
60
|
+
* 缺省 = 不过滤(全量);仅在用户语义明确指定交易状态时由工具层下发。 */
|
|
61
|
+
listPaymentRecords(
|
|
62
|
+
q: ParkingQueryContext,
|
|
63
|
+
page: number,
|
|
64
|
+
pageSize: number,
|
|
65
|
+
orderStatus?: string,
|
|
66
|
+
): Promise<ParkingRecordPage>;
|
|
67
|
+
/** 缴费记录详情;实现必须按当前用户记录列表解析不透明 recordRef */
|
|
68
|
+
getPaymentRecordDetail(
|
|
69
|
+
q: ParkingQueryContext,
|
|
70
|
+
recordRef: string,
|
|
71
|
+
): Promise<ParkingPaymentRecord>;
|
|
72
|
+
/** 当前停车场开票能力 */
|
|
73
|
+
getInvoiceSettings(
|
|
74
|
+
q: ParkingQueryContext,
|
|
75
|
+
): Promise<{ parkingInvoiceEnabled: boolean }>;
|
|
76
|
+
/** 可开票订单查询 */
|
|
77
|
+
listInvoiceableOrders(
|
|
78
|
+
q: ParkingQueryContext,
|
|
79
|
+
page: number,
|
|
80
|
+
pageSize: number,
|
|
81
|
+
): Promise<ParkingRecordPage>;
|
|
82
|
+
/**
|
|
83
|
+
* 停车场综合信息。`pendingOrder` 是 provider 侧仍保留的查询能力,但**卡片层已无此形态**——
|
|
84
|
+
* 待支付单场景改由 queryParkingFee 出可缴费的报价卡,parking-info 的 kind 不含它。
|
|
85
|
+
*/
|
|
86
|
+
queryParkingInfo(
|
|
87
|
+
q: ParkingQueryContext,
|
|
88
|
+
kind: 'chargeRules' | 'benefitPolicy' | 'pendingOrder' | 'vehicleLocation',
|
|
89
|
+
): Promise<ParkingInfo>;
|
|
90
|
+
/** 可选权益(reprice 候选) */
|
|
91
|
+
queryApplicableBenefits(
|
|
92
|
+
q: ParkingQueryContext,
|
|
93
|
+
plateNo: string,
|
|
94
|
+
): Promise<readonly ParkingBenefit[]>;
|
|
95
|
+
/** 寻车定位 */
|
|
96
|
+
locateVehicle(
|
|
97
|
+
q: ParkingQueryContext,
|
|
98
|
+
plateNo: string,
|
|
99
|
+
): Promise<ParkingLocation | null>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ===== 2. ParkingPaymentPort:支付写通道(三步链 R2) =====
|
|
103
|
+
|
|
104
|
+
export interface ParkingPaymentPort {
|
|
105
|
+
/**
|
|
106
|
+
* step1:建单(quoteRef → 厂商支付单)。零元/免支付单可能无 payOrderId(直接 reconcile 收口)。
|
|
107
|
+
* detail:下单所需业务字段(真实链路可能必填 plateNo/payableCents;
|
|
108
|
+
* POC/mock 实现可忽略)。
|
|
109
|
+
*/
|
|
110
|
+
createOrder(
|
|
111
|
+
q: ParkingQueryContext,
|
|
112
|
+
quoteRef: string,
|
|
113
|
+
detail?: {
|
|
114
|
+
plateNo?: string;
|
|
115
|
+
payableCents?: number;
|
|
116
|
+
barcode?: string;
|
|
117
|
+
/** 用户最终勾选的权益引用(跨请求无状态场景必填) */
|
|
118
|
+
benefitRefs?: readonly string[];
|
|
119
|
+
},
|
|
120
|
+
): Promise<{ payOrderId?: string; paymentRef: string }>;
|
|
121
|
+
/** 查厂商单(channel:50 = 支付宝,不支持即拒;payableFeeCents 厂商侧权威金额) */
|
|
122
|
+
getOrderInfo(
|
|
123
|
+
q: ParkingQueryContext,
|
|
124
|
+
paymentRef: string,
|
|
125
|
+
): Promise<{
|
|
126
|
+
orderInfo?: string;
|
|
127
|
+
channel: string;
|
|
128
|
+
payableFeeCents: number | undefined;
|
|
129
|
+
}>;
|
|
130
|
+
/** step2 前置:拉起签名串(orderInfo=my.tradePay tradeNO;payOrderFlowId 供结果上报回传) */
|
|
131
|
+
getPaymentSign(
|
|
132
|
+
q: ParkingQueryContext,
|
|
133
|
+
paymentRef: string,
|
|
134
|
+
): Promise<{ orderInfo: string; payOrderFlowId?: string }>;
|
|
135
|
+
/** step3:客户端结果上报(status 仅上报,不作收口依据;flowId 来自 getPaymentSign) */
|
|
136
|
+
uploadResult(
|
|
137
|
+
q: ParkingQueryContext,
|
|
138
|
+
paymentRef: string,
|
|
139
|
+
status: 'success' | 'cancel' | 'failure' | 'unavailable',
|
|
140
|
+
payOrderFlowId?: string,
|
|
141
|
+
): Promise<void>;
|
|
142
|
+
/** 权威收口唯一源(F5):厂商侧回查支付态 */
|
|
143
|
+
reconcile(
|
|
144
|
+
q: ParkingQueryContext,
|
|
145
|
+
paymentRef: string,
|
|
146
|
+
): Promise<'PAID' | 'PAYING' | 'CANCELLED' | 'UNKNOWN'>;
|
|
147
|
+
/** 权益重选(reprice):新报价(revision 递增)。detail 透传试算业务字段(部分服务必填) */
|
|
148
|
+
reprice(
|
|
149
|
+
q: ParkingQueryContext,
|
|
150
|
+
quoteRef: string,
|
|
151
|
+
benefitRefs: readonly string[],
|
|
152
|
+
detail?: { plateNo?: string; payableCents?: number },
|
|
153
|
+
): Promise<QuoteSnapshot>;
|
|
154
|
+
/** 申请停车发票;实现必须再次校验 recordRef 属于当前用户 */
|
|
155
|
+
applyInvoice(q: ParkingQueryContext, recordRef: string): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ===== 3. ParkingVehiclePort:车辆管理写通道 =====
|
|
159
|
+
|
|
160
|
+
export interface ParkingVehiclePort {
|
|
161
|
+
bindVehicle(q: ParkingQueryContext, plateNo: string): Promise<ParkingVehicle>;
|
|
162
|
+
unbindVehicle(
|
|
163
|
+
q: ParkingQueryContext,
|
|
164
|
+
input: { plateNo?: string; vehicleRef?: string },
|
|
165
|
+
): Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ===== 4. 跨模块停车快览(首页联单面板消费面;W3-3 单一模板统一化) =====
|
|
169
|
+
|
|
170
|
+
/** 跨模块 'parking.snapshot' 词汇(与 service-slots.json 同锁)。 */
|
|
171
|
+
export const PARKING_SNAPSHOT_SERVICE = 'parking.snapshot';
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 停车快览 Port(linked-panel 消费面:绑定车辆列表 + 在场探测)。
|
|
175
|
+
* Pick 派生 = ParkingQueryPort 方法面(签名即契约,不手抄字段);可选能力口
|
|
176
|
+
* (probeParkedVehicles)缺席性随 Pick 保留。parking 模块 register 期注册工厂;
|
|
177
|
+
* 消费方 resolveService 后 await factory(reqCtx) 取 Port。
|
|
178
|
+
*/
|
|
179
|
+
export type ParkingSnapshotPort = Pick<ParkingQueryPort, 'listVehicles' | 'probeParkedVehicles'>;
|
|
180
|
+
|
|
181
|
+
/** 跨模块 'parking.snapshot' 解析工厂(register 期注册,运行期调用)。 */
|
|
182
|
+
export type ParkingSnapshotFactory = (reqCtx: RequestContext) => Promise<ParkingSnapshotPort>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 营销域 Port(015:四 Port 重写,breaking 0.3.0——替换旧单方法 PromotionQueryPort)。
|
|
3
|
+
* 结构类型子集接口——模块完整实现 extends 本接口,消费方仅依赖契约包。
|
|
4
|
+
* C 端主体经 ctx.requestContext.credentials(X2);externalMemberRef 由消费方经 member 域
|
|
5
|
+
* 状态服务解析('member.status' 判定 + externalMemberRefOf 取值,SubjectBindingPort 已 deprecated)。
|
|
6
|
+
*/
|
|
7
|
+
import type { MallScope } from '../scope';
|
|
8
|
+
import type { RequestContext } from '@tbox.cn/app-contracts';
|
|
9
|
+
import type { OfferType, PromotionOffer, PromotionOfferPage, PromotionBestDeal } from '../domain/offer';
|
|
10
|
+
import type { PromotionEntitlement, PromotionEntitlementPage } from '../domain/entitlement';
|
|
11
|
+
import type {
|
|
12
|
+
ActivitySession,
|
|
13
|
+
PromotionActivityDetail,
|
|
14
|
+
PromotionActivityPage,
|
|
15
|
+
} from '../domain/activity';
|
|
16
|
+
import type { PromotionOperation } from '../domain/operation';
|
|
17
|
+
|
|
18
|
+
// ===== 营销域 service slot 词汇(与 service-slots.json 同锁) =====
|
|
19
|
+
|
|
20
|
+
export const PROMOTION_OFFERS_SERVICE = 'promotion.offers';
|
|
21
|
+
export const PROMOTION_ENTITLEMENTS_SERVICE = 'promotion.entitlements';
|
|
22
|
+
export const PROMOTION_ACTIVITIES_SERVICE = 'promotion.activities';
|
|
23
|
+
export const PROMOTION_ACQUISITION_SERVICE = 'promotion.acquisition';
|
|
24
|
+
|
|
25
|
+
/** 查询上下文(scope + 主体 + 请求;credentials 为 C 端用户 Token 唯一来源) */
|
|
26
|
+
export interface PromotionQueryContext {
|
|
27
|
+
readonly scope: MallScope;
|
|
28
|
+
readonly subjectId?: string;
|
|
29
|
+
readonly requestContext: RequestContext;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 活动卡路由策略:provider 按请求 scope 声明是否由专属活动组件承接。
|
|
34
|
+
* 契约只描述能力,不携带厂商名称或卡片实现细节。
|
|
35
|
+
*/
|
|
36
|
+
export const PROMOTION_ACTIVITY_ROUTING_POLICY = 'promotion.activity-routing';
|
|
37
|
+
export type PromotionActivityRoutingPolicy = (reqCtx: RequestContext) => boolean;
|
|
38
|
+
|
|
39
|
+
export interface PromotionSearchInput {
|
|
40
|
+
readonly keyword?: string;
|
|
41
|
+
readonly scene?: string;
|
|
42
|
+
readonly offerTypes?: readonly OfferType[];
|
|
43
|
+
readonly cursor?: string;
|
|
44
|
+
readonly limit?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ===== 1. PromotionOfferQueryPort:Offer 查询/推荐/测算 =====
|
|
48
|
+
|
|
49
|
+
export interface PromotionOfferQueryPort {
|
|
50
|
+
searchOffers(q: PromotionQueryContext, input: PromotionSearchInput): Promise<PromotionOfferPage>;
|
|
51
|
+
/** offerRef 精查;entitlementRef 反查关联 offer;keyword 模糊兜底(LLM 泛指词路由) */
|
|
52
|
+
getOfferDetail(
|
|
53
|
+
q: PromotionQueryContext,
|
|
54
|
+
input: { offerRef?: string; entitlementRef?: string; keyword?: string },
|
|
55
|
+
): Promise<PromotionOffer | null>;
|
|
56
|
+
recommendOffers(q: PromotionQueryContext, input: PromotionSearchInput): Promise<readonly PromotionOffer[]>;
|
|
57
|
+
/** 可选的适用门店明细;无厂商端点时由调用方保留 notice 降级。 */
|
|
58
|
+
listOfferStores?(q: PromotionQueryContext, offerRef: string): Promise<readonly PromotionStoreSummary[]>;
|
|
59
|
+
/** 最优优惠测算(厂商无此能力可缺省;工具侧不应伪造测算卡) */
|
|
60
|
+
calculateBestDeal?(
|
|
61
|
+
q: PromotionQueryContext,
|
|
62
|
+
input: { currentAmountCents: number; candidateOfferRefs?: readonly string[] },
|
|
63
|
+
): Promise<PromotionBestDeal>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PromotionStoreSummary {
|
|
67
|
+
readonly storeId: string;
|
|
68
|
+
readonly storeName: string;
|
|
69
|
+
readonly businessName?: string;
|
|
70
|
+
readonly address?: string;
|
|
71
|
+
readonly floor?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ===== 2. PromotionEntitlementPort:已领取资产 =====
|
|
75
|
+
|
|
76
|
+
export interface PromotionEntitlementPort {
|
|
77
|
+
listEntitlements(q: PromotionQueryContext, input: PromotionSearchInput): Promise<PromotionEntitlementPage>;
|
|
78
|
+
getEntitlementDetail(q: PromotionQueryContext, entitlementRef: string): Promise<PromotionEntitlement | null>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ===== 3. PromotionActivityPort:活动(含场次/指南) =====
|
|
82
|
+
|
|
83
|
+
/** 活动分类(SPEZ-016:活动分类列表;厂商无分类端点时实现方可缺省) */
|
|
84
|
+
export interface PromotionActivityCategoryRecord {
|
|
85
|
+
readonly categoryId: string;
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly parentId?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PromotionActivityPort {
|
|
91
|
+
listActivities(
|
|
92
|
+
q: PromotionQueryContext,
|
|
93
|
+
input: {
|
|
94
|
+
categoryId?: string;
|
|
95
|
+
keyword?: string;
|
|
96
|
+
dataScope?: 'CURRENT' | 'HISTORY';
|
|
97
|
+
registrationRequired?: boolean;
|
|
98
|
+
startsAfter?: string;
|
|
99
|
+
endsBefore?: string;
|
|
100
|
+
cursor?: string;
|
|
101
|
+
limit?: number;
|
|
102
|
+
},
|
|
103
|
+
): Promise<PromotionActivityPage>;
|
|
104
|
+
getActivityDetail(q: PromotionQueryContext, activityRef: string): Promise<PromotionActivityDetail | null>;
|
|
105
|
+
/** 独立场次查询,保留旧营销组件的只读接口语义。 */
|
|
106
|
+
listActivitySessions?(q: PromotionQueryContext, activityRef: string): Promise<readonly ActivitySession[]>;
|
|
107
|
+
/** 活动分类(optional——实现未接分类端点时调用方 ?. 降级) */
|
|
108
|
+
listActivityCategories?(): Promise<readonly PromotionActivityCategoryRecord[]>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ===== 4. PromotionAcquisitionPort:领取写通道 =====
|
|
112
|
+
|
|
113
|
+
export interface PromotionAcquisitionPort {
|
|
114
|
+
/**
|
|
115
|
+
* 领取/报名(写操作)。
|
|
116
|
+
* 幂等:同 idempotencyKey 重复调用返回首果(厂商侧或调用方兜底)。
|
|
117
|
+
* externalMemberRef 由调用方经 member 域状态服务解析(member 域跨模块供给:
|
|
118
|
+
* 'member.status' 判定 + externalMemberRefOf 取真实卡号或 opaque 引用)。
|
|
119
|
+
*/
|
|
120
|
+
acquire(
|
|
121
|
+
q: PromotionQueryContext,
|
|
122
|
+
input: { offerRef: string; externalMemberRef: string; idempotencyKey: string },
|
|
123
|
+
): Promise<PromotionOperation>;
|
|
124
|
+
/** 操作查询(悬挂 PENDING_AUTHORITY 收口用,可缺省) */
|
|
125
|
+
getOperation?(q: PromotionQueryContext, operationRef: string): Promise<PromotionOperation | null>;
|
|
126
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 导购域 Port 接口(011 S7:shopping-guide 6 Port,TIER1 结构类型子集)。
|
|
3
|
+
* 模块完整实现 extends 本接口;消费方(其他模块)仅依赖契约包。
|
|
4
|
+
* 领域模型见 domain/shopping-guide.ts。
|
|
5
|
+
*/
|
|
6
|
+
import type { MallScope } from '../scope';
|
|
7
|
+
import type { RequestContext } from '@tbox.cn/app-contracts';
|
|
8
|
+
import type {
|
|
9
|
+
ProductRecord,
|
|
10
|
+
ProductFacetsRecord,
|
|
11
|
+
ShopRecord,
|
|
12
|
+
CategoryRecord,
|
|
13
|
+
MallDirectoryRecord,
|
|
14
|
+
ShopLocationRecord,
|
|
15
|
+
HumanGuideRecord,
|
|
16
|
+
NearbyMallRecord,
|
|
17
|
+
} from '../domain/shopping-guide';
|
|
18
|
+
|
|
19
|
+
// ===== 导购域 service slot 词汇(与 service-slots.json 同锁) =====
|
|
20
|
+
|
|
21
|
+
export const SHOPPING_CATALOG_SERVICE = 'shopping-guide.catalog';
|
|
22
|
+
export const SHOPPING_SHOPS_SERVICE = 'shopping-guide.shops';
|
|
23
|
+
export const SHOPPING_CATEGORIES_SERVICE = 'shopping-guide.categories';
|
|
24
|
+
export const SHOPPING_DIRECTORY_SERVICE = 'shopping-guide.directory';
|
|
25
|
+
|
|
26
|
+
// ===== 查询上下文(mall scope + 主体) =====
|
|
27
|
+
|
|
28
|
+
export interface ShoppingQueryContext {
|
|
29
|
+
readonly scope: MallScope;
|
|
30
|
+
readonly subjectId?: string;
|
|
31
|
+
readonly requestContext: RequestContext;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ===== 分页 =====
|
|
35
|
+
|
|
36
|
+
export interface ShoppingPage<T> {
|
|
37
|
+
readonly items: readonly T[];
|
|
38
|
+
readonly page: number;
|
|
39
|
+
readonly pageSize: number;
|
|
40
|
+
readonly total: number;
|
|
41
|
+
readonly hasMore: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ===== 1. CatalogQueryPort:商品目录 =====
|
|
45
|
+
|
|
46
|
+
export interface ProductSearchCriteria {
|
|
47
|
+
readonly ctx: ShoppingQueryContext;
|
|
48
|
+
/** 单值关键词(简单调用方兼容);与 keywords 并存时以 keywords 为准 */
|
|
49
|
+
readonly keyword?: string;
|
|
50
|
+
/** 多角度搜索词组(模型拆词产物):provider 应逐词查询并去重合并(OR 语义),禁止拼串 */
|
|
51
|
+
readonly keywords?: readonly string[];
|
|
52
|
+
readonly categoryCode?: string;
|
|
53
|
+
readonly brandCode?: string;
|
|
54
|
+
readonly minPriceMinor?: number;
|
|
55
|
+
readonly maxPriceMinor?: number;
|
|
56
|
+
readonly floorCode?: string;
|
|
57
|
+
readonly buildingCode?: string;
|
|
58
|
+
readonly page: number;
|
|
59
|
+
readonly pageSize: number;
|
|
60
|
+
readonly marketableOnly?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CatalogQueryPort {
|
|
64
|
+
searchProducts(criteria: ProductSearchCriteria): Promise<ShoppingPage<ProductRecord>>;
|
|
65
|
+
getProduct(productId: string, ctx: ShoppingQueryContext): Promise<ProductRecord | null>;
|
|
66
|
+
getFacets(criteria: ProductSearchCriteria): Promise<ProductFacetsRecord>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ===== 2. ShopDirectoryQueryPort:门店目录 =====
|
|
70
|
+
|
|
71
|
+
export interface ShopSearchCriteria {
|
|
72
|
+
readonly ctx: ShoppingQueryContext;
|
|
73
|
+
/** 单值关键词(简单调用方兼容);与 keywords 并存时以 keywords 为准 */
|
|
74
|
+
readonly keyword?: string;
|
|
75
|
+
/** 多角度搜索词组(模型拆词产物):provider 应逐词查询并去重合并(OR 语义),禁止拼串 */
|
|
76
|
+
readonly keywords?: readonly string[];
|
|
77
|
+
readonly brandCode?: string;
|
|
78
|
+
readonly businessCategory?: string;
|
|
79
|
+
readonly floorCode?: string;
|
|
80
|
+
readonly buildingCode?: string;
|
|
81
|
+
readonly page: number;
|
|
82
|
+
readonly pageSize: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ShopDirectoryQueryPort {
|
|
86
|
+
searchShops(criteria: ShopSearchCriteria): Promise<ShoppingPage<ShopRecord>>;
|
|
87
|
+
getShop(shopId: string, ctx: ShoppingQueryContext): Promise<ShopRecord | null>;
|
|
88
|
+
/**
|
|
89
|
+
* 商场概览快照(可选能力口):品牌导览聚合——楼层 + 业态(权威计数)+ 对 C 透出的预览门店。
|
|
90
|
+
* ⚠️ stores 为首页预览子集非全量(概览卡语义),全量浏览走 searchShops。
|
|
91
|
+
* provider 未实现(缺省)→ 概览问句降级 searchShops 零参。
|
|
92
|
+
*/
|
|
93
|
+
fetchStoreOverview?(ctx: ShoppingQueryContext): Promise<MallOverviewSnapshot | null>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 商场概览快照(indexBrandGuideAgg 形态归一):mall-overview preTool 出概览 shop-list 卡的数据源 */
|
|
97
|
+
export interface MallOverviewSnapshot {
|
|
98
|
+
readonly mallId: string;
|
|
99
|
+
/** 楼层名列表(如 B1/1F/2F…,agg.floors) */
|
|
100
|
+
readonly floors: readonly string[];
|
|
101
|
+
/** 业态字典(场景 tag + 权威门店计数,agg.tags→CategoryRecord:name=业态、merchantCount=storeNum) */
|
|
102
|
+
readonly categories: readonly CategoryRecord[];
|
|
103
|
+
/** 预览门店(agg.stores——首页透出口径子集,非全量) */
|
|
104
|
+
readonly stores: readonly ShopRecord[];
|
|
105
|
+
readonly observedAt: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @deprecated 使用 ShopSearchCriteria */
|
|
109
|
+
export type MerchantSearchCriteria = ShopSearchCriteria;
|
|
110
|
+
/** @deprecated 使用 ShopDirectoryQueryPort */
|
|
111
|
+
export type MerchantDirectoryQueryPort = ShopDirectoryQueryPort;
|
|
112
|
+
|
|
113
|
+
// ===== 3. CommercialCategoryQueryPort:业态分类 =====
|
|
114
|
+
|
|
115
|
+
export interface CommercialCategoryQueryPort {
|
|
116
|
+
listCategories(ctx: ShoppingQueryContext): Promise<readonly CategoryRecord[]>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ===== 4. MallDirectoryQueryPort:楼栋-楼层目录 =====
|
|
120
|
+
|
|
121
|
+
export interface MallDirectoryQueryPort {
|
|
122
|
+
getDirectory(ctx: ShoppingQueryContext): Promise<MallDirectoryRecord | null>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ===== 5. AssistanceQueryPort:人工导购协助 =====
|
|
126
|
+
// (客服 FAQ/知识库两方法已拆分至 customer-service.ts CustomerServiceQueryPort——2026-08-31 客服模块拆分)
|
|
127
|
+
|
|
128
|
+
export interface AssistanceQueryPort {
|
|
129
|
+
listHumanGuides(merchantId: string, ctx: ShoppingQueryContext): Promise<readonly HumanGuideRecord[]>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ===== 6. NavigationPort:位置/附近商场/浏览历史 =====
|
|
133
|
+
|
|
134
|
+
export interface NavigationPort {
|
|
135
|
+
getLocation(shopId: string, ctx: ShoppingQueryContext): Promise<ShopLocationRecord | null>;
|
|
136
|
+
listNearbyMalls(ctx: ShoppingQueryContext, query?: string): Promise<readonly NearbyMallRecord[]>;
|
|
137
|
+
listBrowsingHistory(ctx: ShoppingQueryContext, page: number, pageSize: number): Promise<ShoppingPage<ProductRecord>>;
|
|
138
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 商圈通用卡片片段(0.3.2 D4:actionDraftSchema 单源——从 shopping-guide/cards.ts 抽出,
|
|
3
|
+
* 四域条目动作共用;index.ts 与 runtime.ts 均 re-export 本文件,M2 导出面)。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 卡内条目动作草稿(015 F1:判别联合——仅客户端本地语义,write 变体无 token 机制支撑不进)。
|
|
10
|
+
* - send:发消息(value 缺省用 label);link:打开 URL(tel:/https:)。
|
|
11
|
+
* 渲染端经 card-action-handler 既有通道分发(零新机制);zod 判别联合天然拒未知 kind。
|
|
12
|
+
*/
|
|
13
|
+
export const actionDraftSchema = z.discriminatedUnion('kind', [
|
|
14
|
+
z.object({
|
|
15
|
+
kind: z.literal('send'),
|
|
16
|
+
label: z.string().min(1),
|
|
17
|
+
/** 发给服务端 AI 的完整指令(可含隐藏上下文如 merchantId);缺省用 label */
|
|
18
|
+
value: z.string().optional(),
|
|
19
|
+
/** 用户气泡展示文本(有值时 sendMessage 走 visible=false 隐藏,只展示此文本为气泡副本);
|
|
20
|
+
* 缺省则 visible=true,直接展示 value/label */
|
|
21
|
+
visibleValue: z.string().optional(),
|
|
22
|
+
}),
|
|
23
|
+
z.object({ kind: z.literal('link'), label: z.string().min(1), url: z.string().min(1) }),
|
|
24
|
+
]);
|
|
25
|
+
export type ActionDraft = z.infer<typeof actionDraftSchema>;
|