@plolink/sdk 0.0.4

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.
Files changed (76) hide show
  1. package/README.md +259 -0
  2. package/dist/chunk-4H4RACSE.js +335 -0
  3. package/dist/chunk-4H4RACSE.js.map +1 -0
  4. package/dist/chunk-IHAAKFEJ.js +219 -0
  5. package/dist/chunk-IHAAKFEJ.js.map +1 -0
  6. package/dist/chunk-JR4HYYQI.cjs +221 -0
  7. package/dist/chunk-JR4HYYQI.cjs.map +1 -0
  8. package/dist/chunk-MD4O7FWT.js +46 -0
  9. package/dist/chunk-MD4O7FWT.js.map +1 -0
  10. package/dist/chunk-NS3DJP2O.cjs +349 -0
  11. package/dist/chunk-NS3DJP2O.cjs.map +1 -0
  12. package/dist/chunk-Y3UJVC2L.cjs +48 -0
  13. package/dist/chunk-Y3UJVC2L.cjs.map +1 -0
  14. package/dist/client-CAjIQKPm.d.cts +193 -0
  15. package/dist/client-CwNikk7i.d.ts +193 -0
  16. package/dist/common/index.cjs +65 -0
  17. package/dist/common/index.cjs.map +1 -0
  18. package/dist/common/index.d.cts +422 -0
  19. package/dist/common/index.d.ts +422 -0
  20. package/dist/common/index.js +4 -0
  21. package/dist/common/index.js.map +1 -0
  22. package/dist/core-77EbLgbp.d.cts +97 -0
  23. package/dist/core-77EbLgbp.d.ts +97 -0
  24. package/dist/index.cjs +350 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +98 -0
  27. package/dist/index.d.ts +98 -0
  28. package/dist/index.js +306 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/modules/agent/index.cjs +21 -0
  31. package/dist/modules/agent/index.cjs.map +1 -0
  32. package/dist/modules/agent/index.d.cts +71 -0
  33. package/dist/modules/agent/index.d.ts +71 -0
  34. package/dist/modules/agent/index.js +19 -0
  35. package/dist/modules/agent/index.js.map +1 -0
  36. package/dist/modules/billing/index.cjs +413 -0
  37. package/dist/modules/billing/index.cjs.map +1 -0
  38. package/dist/modules/billing/index.d.cts +538 -0
  39. package/dist/modules/billing/index.d.ts +538 -0
  40. package/dist/modules/billing/index.js +411 -0
  41. package/dist/modules/billing/index.js.map +1 -0
  42. package/dist/modules/chat/index.cjs +20 -0
  43. package/dist/modules/chat/index.cjs.map +1 -0
  44. package/dist/modules/chat/index.d.cts +68 -0
  45. package/dist/modules/chat/index.d.ts +68 -0
  46. package/dist/modules/chat/index.js +18 -0
  47. package/dist/modules/chat/index.js.map +1 -0
  48. package/dist/modules/psych/index.cjs +20 -0
  49. package/dist/modules/psych/index.cjs.map +1 -0
  50. package/dist/modules/psych/index.d.cts +69 -0
  51. package/dist/modules/psych/index.d.ts +69 -0
  52. package/dist/modules/psych/index.js +18 -0
  53. package/dist/modules/psych/index.js.map +1 -0
  54. package/dist/modules/rbac/index.cjs +197 -0
  55. package/dist/modules/rbac/index.cjs.map +1 -0
  56. package/dist/modules/rbac/index.d.cts +293 -0
  57. package/dist/modules/rbac/index.d.ts +293 -0
  58. package/dist/modules/rbac/index.js +195 -0
  59. package/dist/modules/rbac/index.js.map +1 -0
  60. package/dist/modules/team/index.cjs +54 -0
  61. package/dist/modules/team/index.cjs.map +1 -0
  62. package/dist/modules/team/index.d.cts +91 -0
  63. package/dist/modules/team/index.d.ts +91 -0
  64. package/dist/modules/team/index.js +52 -0
  65. package/dist/modules/team/index.js.map +1 -0
  66. package/dist/modules/virtual-account/index.cjs +293 -0
  67. package/dist/modules/virtual-account/index.cjs.map +1 -0
  68. package/dist/modules/virtual-account/index.d.cts +366 -0
  69. package/dist/modules/virtual-account/index.d.ts +366 -0
  70. package/dist/modules/virtual-account/index.js +291 -0
  71. package/dist/modules/virtual-account/index.js.map +1 -0
  72. package/dist/poller-BlIRbwL4.d.cts +201 -0
  73. package/dist/poller-DWKZjuSw.d.ts +201 -0
  74. package/dist/shared-6ZepUSPW.d.cts +31 -0
  75. package/dist/shared-6ZepUSPW.d.ts +31 -0
  76. package/package.json +96 -0
@@ -0,0 +1,538 @@
1
+ import { P as PlolinkClient } from '../../client-CAjIQKPm.cjs';
2
+ import { P as Poller } from '../../poller-BlIRbwL4.cjs';
3
+ import '../../core-77EbLgbp.cjs';
4
+ import 'axios';
5
+
6
+ /**
7
+ * Billing 模块类型
8
+ */
9
+ /**
10
+ * 余额信息
11
+ */
12
+ interface BalanceInfo {
13
+ /** 可用余额(字符串,保留精度) */
14
+ balance: string;
15
+ /** 可开票金额 */
16
+ invoiceableAmount: string;
17
+ }
18
+ /**
19
+ * 创建充值请求参数
20
+ */
21
+ interface CreateRechargeParams {
22
+ /** 充值金额(单位:元) */
23
+ amount: number;
24
+ /** 充值描述 */
25
+ description?: string;
26
+ /** 是否需要二维码图片CDN URL */
27
+ needQrCodeImage?: boolean;
28
+ }
29
+ /**
30
+ * 微信充值响应
31
+ */
32
+ interface WechatRechargeResult {
33
+ /** 订单号 */
34
+ orderNo: string;
35
+ /** 二维码URL(weixin://协议) */
36
+ qrCodeUrl: string;
37
+ /** 二维码图片URL(CDN地址,仅当请求时needQrCodeImage=true时返回) */
38
+ qrCodeImageUrl?: string;
39
+ /** 充值记录ID */
40
+ rechargeRecordId: string;
41
+ }
42
+ /**
43
+ * 订单状态
44
+ */
45
+ type OrderStatus = 'pending' | 'success' | 'failed' | 'closed';
46
+ /**
47
+ * 订单查询结果
48
+ */
49
+ interface OrderStatusResult {
50
+ /** 订单号 */
51
+ orderNo: string;
52
+ /** 订单状态 */
53
+ status: OrderStatus;
54
+ /** 充值金额 */
55
+ amount: string;
56
+ /** 创建时间 */
57
+ createdAt: string;
58
+ }
59
+ /**
60
+ * 消耗统计
61
+ */
62
+ interface ConsumptionStats {
63
+ /** 今日消耗 */
64
+ today: string;
65
+ /** 最近7天消耗 */
66
+ last7Days: string;
67
+ /** 最近30天消耗 */
68
+ last30Days: string;
69
+ }
70
+ /**
71
+ * 分页查询参数
72
+ */
73
+ interface PaginationParams {
74
+ /** 页码,从1开始 */
75
+ page?: number;
76
+ /** 每页条数 */
77
+ pageSize?: number;
78
+ }
79
+ /**
80
+ * 分页查询结果
81
+ */
82
+ interface PaginatedResult<T> {
83
+ /** 数据列表 */
84
+ list: T[];
85
+ /** 总条数 */
86
+ total: number;
87
+ }
88
+ /**
89
+ * 消费记录
90
+ */
91
+ interface ConsumptionRecord {
92
+ id: string;
93
+ teamId: string;
94
+ userId: string;
95
+ amount: string;
96
+ purpose: string;
97
+ remark: string;
98
+ createdAt: string;
99
+ }
100
+ /**
101
+ * 充值记录
102
+ */
103
+ interface RechargeRecord {
104
+ id: string;
105
+ teamId: string;
106
+ userId: string;
107
+ amount: string;
108
+ paymentMethod: string;
109
+ orderId?: string;
110
+ remark: string;
111
+ status: OrderStatus;
112
+ createdAt: string;
113
+ }
114
+ /**
115
+ * 开票记录
116
+ */
117
+ interface InvoiceRecord {
118
+ id: string;
119
+ teamId: string;
120
+ amount: string;
121
+ invoiceNumber: string;
122
+ invoiceType: string;
123
+ invoiceTitle: string;
124
+ taxNumber: string;
125
+ fileKey: string;
126
+ remark: string;
127
+ operatorId: string;
128
+ createdAt: string;
129
+ }
130
+ /**
131
+ * 退费记录
132
+ */
133
+ interface RefundRecord {
134
+ id: string;
135
+ teamId: string;
136
+ amount: string;
137
+ remark: string;
138
+ operatorId: string;
139
+ createdAt: string;
140
+ }
141
+ /**
142
+ * 对公账户信息
143
+ */
144
+ interface CompanyAccount {
145
+ accountName: string;
146
+ accountNumber: string;
147
+ bankName: string;
148
+ currency: string;
149
+ }
150
+ /**
151
+ * 跨团队查询参数
152
+ */
153
+ interface CrossTeamQueryParams {
154
+ /** 开始日期(ISO格式,必填) */
155
+ startDate: string;
156
+ /** 结束日期(ISO格式,必填) */
157
+ endDate: string;
158
+ /** 页码,从1开始,默认1 */
159
+ page?: number;
160
+ /** 每页条数,默认20,最大100 */
161
+ pageSize?: number;
162
+ }
163
+ /**
164
+ * 跨团队分页结果
165
+ */
166
+ interface CrossTeamPaginatedResult<T> {
167
+ /** 数据列表 */
168
+ list: T[];
169
+ /** 总条数 */
170
+ total: number;
171
+ /** 当前页码 */
172
+ page: number;
173
+ /** 每页条数 */
174
+ pageSize: number;
175
+ }
176
+ /**
177
+ * 跨团队开票记录(包含团队名称)
178
+ */
179
+ interface InvoiceRecordCrossTeam extends InvoiceRecord {
180
+ /** 团队名称 */
181
+ teamName: string;
182
+ }
183
+ /**
184
+ * 跨团队充值记录(包含团队名称)
185
+ */
186
+ interface RechargeRecordCrossTeam extends RechargeRecord {
187
+ /** 团队名称 */
188
+ teamName: string;
189
+ }
190
+ /**
191
+ * 跨团队消费记录(包含团队名称)
192
+ */
193
+ interface ConsumptionRecordCrossTeam extends ConsumptionRecord {
194
+ /** 团队名称 */
195
+ teamName: string;
196
+ }
197
+ /**
198
+ * 跨团队退费记录(包含团队名称)
199
+ */
200
+ interface RefundRecordCrossTeam extends RefundRecord {
201
+ /** 团队名称 */
202
+ teamName: string;
203
+ }
204
+
205
+ /**
206
+ * 充值消费模块
207
+ *
208
+ * @description
209
+ * 提供完整的充值消费相关功能,包括:
210
+ * - 余额查询
211
+ * - 微信扫码充值(支持可选生成二维码图片)
212
+ * - 订单状态查询和轮询
213
+ * - 消费/充值/开票/退费记录查询
214
+ * - 消耗统计
215
+ * - 对公账户信息
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * import { PlolinkClient } from '@plolink/sdk';
220
+ * import { Billing } from '@plolink/sdk/billing';
221
+ *
222
+ * const client = new PlolinkClient({
223
+ * mode: 'apiKey',
224
+ * apiKey: 'your-api-key'
225
+ * });
226
+ *
227
+ * const billing = new Billing(client);
228
+ *
229
+ * // 查询余额
230
+ * const balance = await billing.getBalance();
231
+ * console.log(`当前余额: ${balance.balance}`);
232
+ *
233
+ * // 发起充值(含二维码图片)
234
+ * const result = await billing.createRecharge({
235
+ * amount: 100,
236
+ * needQrCodeImage: true
237
+ * });
238
+ * console.log(`请扫码支付: ${result.qrCodeImageUrl}`);
239
+ *
240
+ * // 轮询订单状态
241
+ * const poller = billing.watchOrderStatus(result.orderNo, (status) => {
242
+ * console.log(`订单状态: ${status.status}`);
243
+ * if (status.status === 'success') {
244
+ * console.log('支付成功!');
245
+ * }
246
+ * });
247
+ * ```
248
+ *
249
+ * @module billing
250
+ */
251
+
252
+ /**
253
+ * 充值消费模块
254
+ */
255
+ declare class Billing {
256
+ private client;
257
+ constructor(client: PlolinkClient);
258
+ /**
259
+ * 获取团队余额信息
260
+ *
261
+ * @returns 余额信息,包括可用余额和可开票金额
262
+ * @throws {PlolinkError} 当请求失败时抛出
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const balance = await billing.getBalance();
267
+ * console.log(`余额: ${balance.balance}, 可开票: ${balance.invoiceableAmount}`);
268
+ * ```
269
+ */
270
+ getBalance(): Promise<BalanceInfo>;
271
+ /**
272
+ * 创建微信充值订单
273
+ *
274
+ * @param params - 充值参数
275
+ * @returns 充值订单信息,包含二维码URL和订单号
276
+ * @throws {PlolinkError} 当金额无效或创建失败时抛出
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * // 基础充值(仅返回二维码URL)
281
+ * const result = await billing.createRecharge({
282
+ * amount: 100,
283
+ * description: '充值100元'
284
+ * });
285
+ * console.log(`请使用微信扫描: ${result.qrCodeUrl}`);
286
+ *
287
+ * // 充值并获取二维码图片
288
+ * const result = await billing.createRecharge({
289
+ * amount: 100,
290
+ * needQrCodeImage: true
291
+ * });
292
+ * console.log(`二维码图片地址: ${result.qrCodeImageUrl}`);
293
+ * ```
294
+ */
295
+ createRecharge(params: CreateRechargeParams): Promise<WechatRechargeResult>;
296
+ /**
297
+ * 查询订单状态
298
+ *
299
+ * @param orderNo - 订单号
300
+ * @returns 订单状态信息
301
+ * @throws {PlolinkError} 当订单不存在时抛出
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * const status = await billing.getOrderStatus('ORDER_123');
306
+ * console.log(`订单状态: ${status.status}`);
307
+ * ```
308
+ */
309
+ getOrderStatus(orderNo: string): Promise<OrderStatusResult>;
310
+ /**
311
+ * 轮询订单状态
312
+ *
313
+ * @description
314
+ * 创建一个轮询器持续监听订单状态变化,直到订单达到终态(success/failed/closed)。
315
+ * 使用指数退避策略,初始间隔1秒,最大间隔30秒。
316
+ *
317
+ * @param orderNo - 订单号
318
+ * @param onStatusChange - 状态变化回调函数
319
+ * @returns Poller实例,可用于手动停止轮询
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const poller = billing.watchOrderStatus('ORDER_123', (status) => {
324
+ * console.log(`状态更新: ${status.status}`);
325
+ *
326
+ * if (status.status === 'success') {
327
+ * console.log('支付成功!');
328
+ * } else if (status.status === 'failed') {
329
+ * console.error('支付失败!');
330
+ * }
331
+ * });
332
+ *
333
+ * // 开始轮询
334
+ * poller.start();
335
+ *
336
+ * // 需要时可以手动停止
337
+ * // poller.stop();
338
+ * ```
339
+ */
340
+ watchOrderStatus(orderNo: string, onStatusChange: (status: OrderStatusResult) => void): Poller<OrderStatusResult>;
341
+ /**
342
+ * 获取消耗统计
343
+ *
344
+ * @returns 消耗统计信息(今日/7天/30天)
345
+ * @throws {PlolinkError} 当请求失败时抛出
346
+ *
347
+ * @example
348
+ * ```typescript
349
+ * const stats = await billing.getConsumptionStats();
350
+ * console.log(`今日消耗: ${stats.today}`);
351
+ * console.log(`7天消耗: ${stats.last7Days}`);
352
+ * console.log(`30天消耗: ${stats.last30Days}`);
353
+ * ```
354
+ */
355
+ getConsumptionStats(): Promise<ConsumptionStats>;
356
+ /**
357
+ * 获取消费记录(分页)
358
+ *
359
+ * @param params - 分页参数
360
+ * @returns 消费记录列表和总数
361
+ * @throws {PlolinkError} 当请求失败时抛出
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const result = await billing.getConsumptionRecords({
366
+ * page: 1,
367
+ * pageSize: 20
368
+ * });
369
+ * console.log(`共 ${result.total} 条记录`);
370
+ * result.list.forEach(record => {
371
+ * console.log(`消费: ${record.amount}, 用途: ${record.purpose}`);
372
+ * });
373
+ * ```
374
+ */
375
+ getConsumptionRecords(params?: PaginationParams): Promise<PaginatedResult<ConsumptionRecord>>;
376
+ /**
377
+ * 获取充值记录
378
+ *
379
+ * @returns 充值记录列表
380
+ * @throws {PlolinkError} 当请求失败时抛出
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * const records = await billing.getRechargeRecords();
385
+ * records.forEach(record => {
386
+ * console.log(`${record.createdAt}: ${record.amount}, 状态: ${record.status}`);
387
+ * });
388
+ * ```
389
+ */
390
+ getRechargeRecords(): Promise<RechargeRecord[]>;
391
+ /**
392
+ * 获取开票记录
393
+ *
394
+ * @returns 开票记录列表
395
+ * @throws {PlolinkError} 当请求失败时抛出
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * const records = await billing.getInvoiceRecords();
400
+ * records.forEach(record => {
401
+ * console.log(`发票: ${record.invoiceNumber}, 金额: ${record.amount}`);
402
+ * });
403
+ * ```
404
+ */
405
+ getInvoiceRecords(): Promise<InvoiceRecord[]>;
406
+ /**
407
+ * 获取退费记录
408
+ *
409
+ * @returns 退费记录列表
410
+ * @throws {PlolinkError} 当请求失败时抛出
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const records = await billing.getRefundRecords();
415
+ * records.forEach(record => {
416
+ * console.log(`退费: ${record.amount}, 备注: ${record.remark}`);
417
+ * });
418
+ * ```
419
+ */
420
+ getRefundRecords(): Promise<RefundRecord[]>;
421
+ /**
422
+ * 获取对公账户信息
423
+ *
424
+ * @returns 对公账户信息
425
+ * @throws {PlolinkError} 当请求失败时抛出
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * const account = await billing.getCompanyAccount();
430
+ * console.log(`账户名: ${account.accountName}`);
431
+ * console.log(`账号: ${account.accountNumber}`);
432
+ * console.log(`开户行: ${account.bankName}`);
433
+ * ```
434
+ */
435
+ getCompanyAccount(): Promise<CompanyAccount>;
436
+ /**
437
+ * 获取跨团队开票记录
438
+ *
439
+ * 用于财务人员在多个团队中拥有财务权限时查询所有可管理团队的开票记录
440
+ *
441
+ * @param params - 查询参数
442
+ * @returns 分页结果(包含团队名称)
443
+ * @throws {PlolinkError} 当参数无效或请求失败时抛出
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * const result = await billing.getInvoiceRecordsCrossTeam({
448
+ * startDate: '2025-01-01T00:00:00.000Z',
449
+ * endDate: '2025-01-31T23:59:59.999Z',
450
+ * page: 1,
451
+ * pageSize: 20
452
+ * });
453
+ *
454
+ * console.log(`共查询到 ${result.total} 条记录`);
455
+ * result.list.forEach(record => {
456
+ * console.log(`团队: ${record.teamName}, 发票号: ${record.invoiceNumber}, 金额: ${record.amount}`);
457
+ * });
458
+ * ```
459
+ */
460
+ getInvoiceRecordsCrossTeam(params: CrossTeamQueryParams): Promise<CrossTeamPaginatedResult<InvoiceRecordCrossTeam>>;
461
+ /**
462
+ * 获取跨团队充值记录
463
+ *
464
+ * 用于财务人员在多个团队中拥有财务权限时查询所有可管理团队的充值记录
465
+ *
466
+ * @param params - 查询参数
467
+ * @returns 分页结果(包含团队名称)
468
+ * @throws {PlolinkError} 当参数无效或请求失败时抛出
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * const result = await billing.getRechargeRecordsCrossTeam({
473
+ * startDate: '2025-01-01T00:00:00.000Z',
474
+ * endDate: '2025-01-31T23:59:59.999Z',
475
+ * page: 1,
476
+ * pageSize: 20
477
+ * });
478
+ *
479
+ * console.log(`共查询到 ${result.total} 条记录`);
480
+ * result.list.forEach(record => {
481
+ * console.log(`团队: ${record.teamName}, 金额: ${record.amount}, 状态: ${record.status}`);
482
+ * });
483
+ * ```
484
+ */
485
+ getRechargeRecordsCrossTeam(params: CrossTeamQueryParams): Promise<CrossTeamPaginatedResult<RechargeRecordCrossTeam>>;
486
+ /**
487
+ * 获取跨团队消费记录
488
+ *
489
+ * 用于财务人员在多个团队中拥有财务权限时查询所有可管理团队的消费记录
490
+ *
491
+ * @param params - 查询参数
492
+ * @returns 分页结果(包含团队名称)
493
+ * @throws {PlolinkError} 当参数无效或请求失败时抛出
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const result = await billing.getConsumptionRecordsCrossTeam({
498
+ * startDate: '2025-01-01T00:00:00.000Z',
499
+ * endDate: '2025-01-31T23:59:59.999Z',
500
+ * page: 1,
501
+ * pageSize: 20
502
+ * });
503
+ *
504
+ * console.log(`共查询到 ${result.total} 条记录`);
505
+ * result.list.forEach(record => {
506
+ * console.log(`团队: ${record.teamName}, 金额: ${record.amount}, 用途: ${record.purpose}`);
507
+ * });
508
+ * ```
509
+ */
510
+ getConsumptionRecordsCrossTeam(params: CrossTeamQueryParams): Promise<CrossTeamPaginatedResult<ConsumptionRecordCrossTeam>>;
511
+ /**
512
+ * 获取跨团队退费记录
513
+ *
514
+ * 用于财务人员在多个团队中拥有财务权限时查询所有可管理团队的退费记录
515
+ *
516
+ * @param params - 查询参数
517
+ * @returns 分页结果(包含团队名称)
518
+ * @throws {PlolinkError} 当参数无效或请求失败时抛出
519
+ *
520
+ * @example
521
+ * ```typescript
522
+ * const result = await billing.getRefundRecordsCrossTeam({
523
+ * startDate: '2025-01-01T00:00:00.000Z',
524
+ * endDate: '2025-01-31T23:59:59.999Z',
525
+ * page: 1,
526
+ * pageSize: 20
527
+ * });
528
+ *
529
+ * console.log(`共查询到 ${result.total} 条记录`);
530
+ * result.list.forEach(record => {
531
+ * console.log(`团队: ${record.teamName}, 金额: ${record.amount}, 备注: ${record.remark}`);
532
+ * });
533
+ * ```
534
+ */
535
+ getRefundRecordsCrossTeam(params: CrossTeamQueryParams): Promise<CrossTeamPaginatedResult<RefundRecordCrossTeam>>;
536
+ }
537
+
538
+ export { type BalanceInfo, Billing, type CompanyAccount, type ConsumptionRecord, type ConsumptionRecordCrossTeam, type ConsumptionStats, type CreateRechargeParams, type CrossTeamPaginatedResult, type CrossTeamQueryParams, type InvoiceRecord, type InvoiceRecordCrossTeam, type OrderStatus, type OrderStatusResult, type PaginatedResult, type PaginationParams, type RechargeRecord, type RechargeRecordCrossTeam, type RefundRecord, type RefundRecordCrossTeam, type WechatRechargeResult };