@deallony/shared 1.2.67 → 1.2.69

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 (30) hide show
  1. package/dist/cjs/constants/routes/app.d.ts +3 -0
  2. package/dist/cjs/constants/routes/app.js +1 -0
  3. package/dist/cjs/dto/ai/gemini-api-key.dto.d.ts +4 -1
  4. package/dist/cjs/schema/ad/save-ad.schema.d.ts +31 -0
  5. package/dist/cjs/schema/ad/save-ad.schema.js +22 -1
  6. package/dist/cjs/schema/ai/gemini-api-key.schema.d.ts +46 -3
  7. package/dist/cjs/schema/ai/gemini-api-key.schema.js +36 -9
  8. package/dist/cjs/schema/communication/index.d.ts +1 -0
  9. package/dist/cjs/schema/communication/index.js +1 -0
  10. package/dist/cjs/schema/communication/offer.schema.d.ts +49 -0
  11. package/dist/cjs/schema/communication/offer.schema.js +31 -0
  12. package/dist/cjs/types/ad/Ad.d.ts +21 -0
  13. package/dist/cjs/types/ad-common/PriceType.d.ts +2 -0
  14. package/dist/cjs/types/common/MetaData.d.ts +3 -0
  15. package/dist/cjs/types/communication/Chat.d.ts +30 -1
  16. package/dist/cjs/types/communication/Chat.js +10 -1
  17. package/dist/cjs/types/integrations/GeminiApiKey.d.ts +11 -2
  18. package/dist/cjs/utils/index.d.ts +1 -0
  19. package/dist/cjs/utils/index.js +1 -0
  20. package/dist/cjs/utils/rentPrices.d.ts +8 -0
  21. package/dist/cjs/utils/rentPrices.js +33 -0
  22. package/dist/esm/constants/routes/app.js +1 -0
  23. package/dist/esm/schema/ad/save-ad.schema.js +21 -0
  24. package/dist/esm/schema/ai/gemini-api-key.schema.js +35 -8
  25. package/dist/esm/schema/communication/index.js +1 -0
  26. package/dist/esm/schema/communication/offer.schema.js +28 -0
  27. package/dist/esm/types/communication/Chat.js +9 -0
  28. package/dist/esm/utils/index.js +1 -0
  29. package/dist/esm/utils/rentPrices.js +26 -0
  30. package/package.json +58 -58
@@ -300,6 +300,8 @@ export declare const APP_ROUTES: {
300
300
  by_message: string;
301
301
  by_whatsapp: string;
302
302
  phone: string;
303
+ /** Rent ads: JSON of [{ price_type_id, price }]; the modal then sends a rent offer */
304
+ rent_prices?: string;
303
305
  }) => {
304
306
  pathname: string;
305
307
  params: {
@@ -312,6 +314,7 @@ export declare const APP_ROUTES: {
312
314
  by_whatsapp: string;
313
315
  phone: string;
314
316
  ad_price: number;
317
+ rent_prices: string | undefined;
315
318
  };
316
319
  };
317
320
  };
@@ -138,6 +138,7 @@ exports.APP_ROUTES = {
138
138
  by_whatsapp: props.by_whatsapp,
139
139
  phone: props.phone,
140
140
  ad_price: props.ad_price,
141
+ rent_prices: props.rent_prices,
141
142
  },
142
143
  }),
143
144
  },
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createGeminiApiKeySchema, geminiApiKeyIdBodySchema, leasedGeminiApiKeySchema, leaseGeminiApiKeySchema, reorderGeminiApiKeysSchema, reportGeminiApiKeyUsageSchema, updateGeminiApiKeySchema } from '../../schema/ai/gemini-api-key.schema';
2
+ import { aiKeySettingsSchema, aiProviderSchema, aiKeySelectionModeSchema, createGeminiApiKeySchema, geminiApiKeyIdBodySchema, leasedGeminiApiKeySchema, leaseGeminiApiKeySchema, reorderGeminiApiKeysSchema, reportGeminiApiKeyUsageSchema, updateGeminiApiKeySchema } from '../../schema/ai/gemini-api-key.schema';
3
3
  export type CreateGeminiApiKeyDto = z.input<typeof createGeminiApiKeySchema>;
4
4
  export type UpdateGeminiApiKeyDto = z.input<typeof updateGeminiApiKeySchema>;
5
5
  export type GeminiApiKeyIdBodyDto = z.input<typeof geminiApiKeyIdBodySchema>;
@@ -7,3 +7,6 @@ export type ReorderGeminiApiKeysDto = z.input<typeof reorderGeminiApiKeysSchema>
7
7
  export type LeaseGeminiApiKeyDto = z.input<typeof leaseGeminiApiKeySchema>;
8
8
  export type LeasedGeminiApiKey = z.infer<typeof leasedGeminiApiKeySchema>;
9
9
  export type ReportGeminiApiKeyUsageDto = z.input<typeof reportGeminiApiKeyUsageSchema>;
10
+ export type AiProvider = z.infer<typeof aiProviderSchema>;
11
+ export type AiKeySelectionMode = z.infer<typeof aiKeySelectionModeSchema>;
12
+ export type AiKeySettingsDto = z.input<typeof aiKeySettingsSchema>;
@@ -6,6 +6,17 @@ export declare const subjectTypeSchema: z.ZodEnum<{
6
6
  used_products: "used_products";
7
7
  }>;
8
8
  export declare const adMediaSchema: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodAny>>>;
9
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
10
+ export declare const adRentPriceSchema: z.ZodObject<{
11
+ price_type_id: z.ZodCoercedNumber<unknown>;
12
+ price: z.ZodCoercedNumber<unknown>;
13
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
14
+ }, z.core.$strip>;
15
+ export declare const adRentPricesSchema: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
16
+ price_type_id: z.ZodCoercedNumber<unknown>;
17
+ price: z.ZodCoercedNumber<unknown>;
18
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
19
+ }, z.core.$strip>>>>;
9
20
  export declare const createAdSchema: z.ZodObject<{
10
21
  motor: z.ZodNullable<z.ZodOptional<z.ZodObject<{
11
22
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -98,6 +109,11 @@ export declare const createAdSchema: z.ZodObject<{
98
109
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
99
110
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
100
111
  }, z.core.$strip>>>>;
112
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
113
+ price_type_id: z.ZodCoercedNumber<unknown>;
114
+ price: z.ZodCoercedNumber<unknown>;
115
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
116
+ }, z.core.$strip>>>>;
101
117
  user_id: z.ZodCoercedNumber<unknown>;
102
118
  id: z.ZodOptional<z.ZodNull>;
103
119
  }, z.core.$strip>;
@@ -197,6 +213,11 @@ export declare const updateAdSchema: z.ZodObject<{
197
213
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
198
214
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
199
215
  }, z.core.$strip>>>>;
216
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
217
+ price_type_id: z.ZodCoercedNumber<unknown>;
218
+ price: z.ZodCoercedNumber<unknown>;
219
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
220
+ }, z.core.$strip>>>>;
200
221
  user_id: z.ZodCoercedNumber<unknown>;
201
222
  }, z.core.$strip>;
202
223
  export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
@@ -291,6 +312,11 @@ export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
291
312
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
292
313
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
293
314
  }, z.core.$strip>>>>;
315
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
316
+ price_type_id: z.ZodCoercedNumber<unknown>;
317
+ price: z.ZodCoercedNumber<unknown>;
318
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
319
+ }, z.core.$strip>>>>;
294
320
  user_id: z.ZodCoercedNumber<unknown>;
295
321
  id: z.ZodOptional<z.ZodNull>;
296
322
  }, z.core.$strip>, z.ZodObject<{
@@ -389,5 +415,10 @@ export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
389
415
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
390
416
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
391
417
  }, z.core.$strip>>>>;
418
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
419
+ price_type_id: z.ZodCoercedNumber<unknown>;
420
+ price: z.ZodCoercedNumber<unknown>;
421
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
422
+ }, z.core.$strip>>>>;
392
423
  user_id: z.ZodCoercedNumber<unknown>;
393
424
  }, z.core.$strip>]>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveAdSchema = exports.updateAdSchema = exports.createAdSchema = exports.adMediaSchema = exports.subjectTypeSchema = void 0;
3
+ exports.saveAdSchema = exports.updateAdSchema = exports.createAdSchema = exports.adRentPricesSchema = exports.adRentPriceSchema = exports.adMediaSchema = exports.subjectTypeSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const AdMedia_1 = require("../../constants/ad/AdMedia");
6
6
  const adMedia_1 = require("../../utils/adMedia");
@@ -166,6 +166,26 @@ exports.adMediaSchema = zod_1.z
166
166
  })
167
167
  .optional()
168
168
  .nullable();
169
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
170
+ exports.adRentPriceSchema = zod_1.z.object({
171
+ price_type_id: zod_1.z.coerce.number().int().positive(),
172
+ price: zod_1.z.coerce.number().positive(),
173
+ is_main: zod_1.z.coerce.boolean().optional(),
174
+ });
175
+ exports.adRentPricesSchema = zod_1.z
176
+ .array(exports.adRentPriceSchema)
177
+ .max(10)
178
+ .superRefine((prices, ctx) => {
179
+ const ids = prices.map((item) => item.price_type_id);
180
+ if (new Set(ids).size !== ids.length) {
181
+ ctx.addIssue({ code: "custom", message: "Each rent period can be listed once" });
182
+ }
183
+ if (prices.filter((item) => item.is_main).length > 1) {
184
+ ctx.addIssue({ code: "custom", message: "Only one rent price can be the main price" });
185
+ }
186
+ })
187
+ .optional()
188
+ .nullable();
169
189
  const baseAdCommon = zod_1.z.object({
170
190
  title: zod_1.z.string().trim().optional(),
171
191
  status_id: zod_1.z.coerce.number().int().positive().optional(),
@@ -197,6 +217,7 @@ const baseAdCommon = zod_1.z.object({
197
217
  company_id: zod_1.z.coerce.number().int().positive().optional().nullable(),
198
218
  expires_at: zod_1.z.iso.datetime().optional().nullable(),
199
219
  adLocations: zod_1.z.array(adLocationSchema).optional().nullable(),
220
+ rent_prices: exports.adRentPricesSchema,
200
221
  });
201
222
  exports.createAdSchema = zod_1.z.object({
202
223
  user_id: zod_1.z.coerce.number().int().positive(),
@@ -1,8 +1,29 @@
1
1
  import { z } from 'zod';
2
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
2
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
3
+ export declare const AI_PROVIDERS: readonly ["gemini", "anthropic"];
4
+ export declare const aiProviderSchema: z.ZodEnum<{
5
+ gemini: "gemini";
6
+ anthropic: "anthropic";
7
+ }>;
8
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
9
+ export declare const AI_PROVIDER_MODELS: Record<(typeof AI_PROVIDERS)[number], readonly string[]>;
10
+ /**
11
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
12
+ * order, `balanced` takes the key with the fewest tokens used today.
13
+ */
14
+ export declare const AI_KEY_SELECTION_MODES: readonly ["priority", "balanced"];
15
+ export declare const aiKeySelectionModeSchema: z.ZodEnum<{
16
+ priority: "priority";
17
+ balanced: "balanced";
18
+ }>;
19
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
3
20
  export declare const geminiModelNameSchema: z.ZodString;
4
21
  export declare const createGeminiApiKeySchema: z.ZodObject<{
5
22
  name: z.ZodString;
23
+ provider: z.ZodDefault<z.ZodEnum<{
24
+ gemini: "gemini";
25
+ anthropic: "anthropic";
26
+ }>>;
6
27
  api_key: z.ZodString;
7
28
  model_name: z.ZodString;
8
29
  is_active: z.ZodDefault<z.ZodBoolean>;
@@ -11,6 +32,10 @@ export declare const createGeminiApiKeySchema: z.ZodObject<{
11
32
  export declare const updateGeminiApiKeySchema: z.ZodObject<{
12
33
  id: z.ZodCoercedNumber<unknown>;
13
34
  name: z.ZodOptional<z.ZodString>;
35
+ provider: z.ZodOptional<z.ZodEnum<{
36
+ gemini: "gemini";
37
+ anthropic: "anthropic";
38
+ }>>;
14
39
  api_key: z.ZodOptional<z.ZodString>;
15
40
  model_name: z.ZodOptional<z.ZodString>;
16
41
  is_active: z.ZodOptional<z.ZodBoolean>;
@@ -22,16 +47,34 @@ export declare const geminiApiKeyIdBodySchema: z.ZodObject<{
22
47
  export declare const reorderGeminiApiKeysSchema: z.ZodObject<{
23
48
  ids: z.ZodArray<z.ZodCoercedNumber<unknown>>;
24
49
  }, z.core.$strip>;
25
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
50
+ export declare const aiKeySettingsSchema: z.ZodObject<{
51
+ selection_mode: z.ZodEnum<{
52
+ priority: "priority";
53
+ balanced: "balanced";
54
+ }>;
55
+ }, z.core.$strip>;
56
+ /**
57
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
58
+ * this call, so a retry moves on to the next key in the order.
59
+ */
26
60
  export declare const leaseGeminiApiKeySchema: z.ZodObject<{
27
61
  model_name: z.ZodOptional<z.ZodString>;
62
+ providers: z.ZodOptional<z.ZodArray<z.ZodEnum<{
63
+ gemini: "gemini";
64
+ anthropic: "anthropic";
65
+ }>>>;
66
+ exclude_ids: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
28
67
  }, z.core.$strip>;
29
68
  export declare const leasedGeminiApiKeySchema: z.ZodObject<{
30
69
  id: z.ZodNumber;
70
+ provider: z.ZodEnum<{
71
+ gemini: "gemini";
72
+ anthropic: "anthropic";
73
+ }>;
31
74
  api_key: z.ZodString;
32
75
  model_name: z.ZodString;
33
76
  }, z.core.$strip>;
34
- /** Sent after every Gemini call made with a leased key. */
77
+ /** Sent after every AI call made with a leased key. */
35
78
  export declare const reportGeminiApiKeyUsageSchema: z.ZodObject<{
36
79
  id: z.ZodCoercedNumber<unknown>;
37
80
  total_tokens: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -1,18 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.reportGeminiApiKeyUsageSchema = exports.leasedGeminiApiKeySchema = exports.leaseGeminiApiKeySchema = exports.reorderGeminiApiKeysSchema = exports.geminiApiKeyIdBodySchema = exports.updateGeminiApiKeySchema = exports.createGeminiApiKeySchema = exports.geminiModelNameSchema = void 0;
3
+ exports.reportGeminiApiKeyUsageSchema = exports.leasedGeminiApiKeySchema = exports.leaseGeminiApiKeySchema = exports.aiKeySettingsSchema = exports.reorderGeminiApiKeysSchema = exports.geminiApiKeyIdBodySchema = exports.updateGeminiApiKeySchema = exports.createGeminiApiKeySchema = exports.geminiModelNameSchema = exports.aiKeySelectionModeSchema = exports.AI_KEY_SELECTION_MODES = exports.AI_PROVIDER_MODELS = exports.aiProviderSchema = exports.AI_PROVIDERS = void 0;
4
4
  const zod_1 = require("zod");
5
5
  /**
6
- * Gemini API keys are stored in backend-core (one table) and shared by every Deallony service.
7
- * The dashboard manages them through the admin schemas; other services lease a key and report
8
- * the tokens they used through the internal schemas.
6
+ * AI API keys (Gemini and Claude) are stored in backend-core (one table) and shared by every
7
+ * Deallony service. The dashboard manages them through the admin schemas; other services lease a
8
+ * key and report the tokens they used through the internal schemas.
9
9
  */
10
10
  const geminiApiKeyIdSchema = zod_1.z.coerce.number().int().positive();
11
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
11
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
12
+ exports.AI_PROVIDERS = ['gemini', 'anthropic'];
13
+ exports.aiProviderSchema = zod_1.z.enum(exports.AI_PROVIDERS);
14
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
15
+ exports.AI_PROVIDER_MODELS = {
16
+ gemini: ['gemini-3.5-flash-lite', 'gemini-3.1-flash-lite'],
17
+ anthropic: ['claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5'],
18
+ };
19
+ /**
20
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
21
+ * order, `balanced` takes the key with the fewest tokens used today.
22
+ */
23
+ exports.AI_KEY_SELECTION_MODES = ['priority', 'balanced'];
24
+ exports.aiKeySelectionModeSchema = zod_1.z.enum(exports.AI_KEY_SELECTION_MODES);
25
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
12
26
  exports.geminiModelNameSchema = zod_1.z.string().trim().min(1).max(100);
13
27
  // ---------- Admin (dashboard) ----------
14
28
  exports.createGeminiApiKeySchema = zod_1.z.object({
15
29
  name: zod_1.z.string().trim().min(1).max(255),
30
+ provider: exports.aiProviderSchema.default('gemini'),
16
31
  api_key: zod_1.z.string().trim().min(20).max(512),
17
32
  model_name: exports.geminiModelNameSchema,
18
33
  is_active: zod_1.z.boolean().default(true),
@@ -21,6 +36,7 @@ exports.createGeminiApiKeySchema = zod_1.z.object({
21
36
  exports.updateGeminiApiKeySchema = zod_1.z.object({
22
37
  id: geminiApiKeyIdSchema,
23
38
  name: zod_1.z.string().trim().min(1).max(255).optional(),
39
+ provider: exports.aiProviderSchema.optional(),
24
40
  api_key: zod_1.z.string().trim().min(20).max(512).optional(),
25
41
  model_name: exports.geminiModelNameSchema.optional(),
26
42
  is_active: zod_1.z.boolean().optional(),
@@ -32,23 +48,34 @@ exports.geminiApiKeyIdBodySchema = zod_1.z.object({
32
48
  exports.reorderGeminiApiKeysSchema = zod_1.z.object({
33
49
  ids: zod_1.z.array(geminiApiKeyIdSchema).min(1),
34
50
  });
51
+ exports.aiKeySettingsSchema = zod_1.z.object({
52
+ selection_mode: exports.aiKeySelectionModeSchema,
53
+ });
35
54
  // ---------- Internal (other services) ----------
36
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
55
+ /**
56
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
57
+ * this call, so a retry moves on to the next key in the order.
58
+ */
37
59
  exports.leaseGeminiApiKeySchema = zod_1.z.object({
60
+ /** Only keys whose default model is this one */
38
61
  model_name: exports.geminiModelNameSchema.optional(),
62
+ /** Only keys of these providers; all providers when left out */
63
+ providers: zod_1.z.array(exports.aiProviderSchema).min(1).optional(),
64
+ exclude_ids: zod_1.z.array(geminiApiKeyIdSchema).optional(),
39
65
  });
40
66
  exports.leasedGeminiApiKeySchema = zod_1.z.object({
41
67
  id: zod_1.z.number().int().positive(),
68
+ provider: exports.aiProviderSchema,
42
69
  api_key: zod_1.z.string(),
43
70
  model_name: zod_1.z.string(),
44
71
  });
45
- /** Sent after every Gemini call made with a leased key. */
72
+ /** Sent after every AI call made with a leased key. */
46
73
  exports.reportGeminiApiKeyUsageSchema = zod_1.z.object({
47
74
  id: geminiApiKeyIdSchema,
48
- /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
75
+ /** Total tokens of the response (input + output); 0 when the call failed */
49
76
  total_tokens: zod_1.z.coerce.number().int().min(0).default(0),
50
77
  success: zod_1.z.boolean().default(true),
51
- /** HTTP status Gemini answered with, e.g. 429 */
78
+ /** HTTP status the provider answered with, e.g. 429 */
52
79
  error_code: zod_1.z.coerce.number().int().optional(),
53
80
  error_message: zod_1.z.string().trim().max(2000).optional(),
54
81
  });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-conversation.schema';
2
2
  export * from './support-ticket.schema';
3
+ export * from './offer.schema';
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ai-conversation.schema"), exports);
18
18
  __exportStar(require("./support-ticket.schema"), exports);
19
+ __exportStar(require("./offer.schema"), exports);
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ export declare const offerStatusSchema: z.ZodEnum<{
3
+ pending: "pending";
4
+ accepted: "accepted";
5
+ countered: "countered";
6
+ declined: "declined";
7
+ }>;
8
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
9
+ export declare const priceOfferPayloadSchema: z.ZodObject<{
10
+ provider: z.ZodObject<{
11
+ offered_price: z.ZodCoercedNumber<unknown>;
12
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
13
+ }, z.core.$strip>;
14
+ requester: z.ZodObject<{
15
+ offered_price: z.ZodCoercedNumber<unknown>;
16
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
17
+ }, z.core.$strip>;
18
+ status: z.ZodOptional<z.ZodEnum<{
19
+ pending: "pending";
20
+ accepted: "accepted";
21
+ countered: "countered";
22
+ declined: "declined";
23
+ }>>;
24
+ reply_to_message_id: z.ZodNullable<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
25
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
26
+ }, z.core.$strip>;
27
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
28
+ export declare const rentOfferPayloadSchema: z.ZodObject<{
29
+ provider: z.ZodObject<{
30
+ offered_price: z.ZodCoercedNumber<unknown>;
31
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
32
+ }, z.core.$strip>;
33
+ requester: z.ZodObject<{
34
+ offered_price: z.ZodCoercedNumber<unknown>;
35
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
36
+ }, z.core.$strip>;
37
+ status: z.ZodOptional<z.ZodEnum<{
38
+ pending: "pending";
39
+ accepted: "accepted";
40
+ countered: "countered";
41
+ declined: "declined";
42
+ }>>;
43
+ reply_to_message_id: z.ZodNullable<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
44
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
45
+ price_type_id: z.ZodCoercedNumber<unknown>;
46
+ currency_id: z.ZodCoercedNumber<unknown>;
47
+ duration: z.ZodCoercedNumber<unknown>;
48
+ start_date: z.ZodNullable<z.ZodOptional<z.ZodString>>;
49
+ }, z.core.$strip>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rentOfferPayloadSchema = exports.priceOfferPayloadSchema = exports.offerStatusSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.offerStatusSchema = zod_1.z.enum(["pending", "accepted", "countered", "declined"]);
6
+ // The seller's side can be 0 when the ad has no price; the backend requires a positive buyer price
7
+ const offerSideSchema = zod_1.z.object({
8
+ offered_price: zod_1.z.coerce.number().min(0),
9
+ currency_id: zod_1.z.coerce.number().int().positive().optional(),
10
+ });
11
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
12
+ exports.priceOfferPayloadSchema = zod_1.z.object({
13
+ provider: offerSideSchema,
14
+ requester: offerSideSchema,
15
+ status: exports.offerStatusSchema.optional(),
16
+ reply_to_message_id: zod_1.z.coerce.number().int().positive().optional().nullable(),
17
+ /** Free text the sender added; the message text itself is a readable summary for older apps */
18
+ note: zod_1.z.string().trim().max(1000).optional().nullable(),
19
+ });
20
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
21
+ exports.rentOfferPayloadSchema = exports.priceOfferPayloadSchema.extend({
22
+ price_type_id: zod_1.z.coerce.number().int().positive(),
23
+ currency_id: zod_1.z.coerce.number().int().positive(),
24
+ duration: zod_1.z.coerce.number().int().min(1).max(1000),
25
+ start_date: zod_1.z
26
+ .string()
27
+ .trim()
28
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
29
+ .optional()
30
+ .nullable(),
31
+ });
@@ -9,6 +9,19 @@ import { IUsedProduct } from "../used-product/UsedProduct";
9
9
  import { Category, Currency } from "..";
10
10
  import { IAdminBoundary } from "../geo";
11
11
  import type { ICompany } from "../company/Company";
12
+ import type { IPriceType } from "../ad-common/PriceType";
13
+ /** One rent price of an ad for one period (a price type linked to "For Rent", e.g. Daily). */
14
+ export type IAdRentPrice = {
15
+ id?: number;
16
+ ad_id?: number;
17
+ price_type_id: number;
18
+ price: number;
19
+ /** price converted to the base currency, used for filtering and sorting */
20
+ base_price?: number | null;
21
+ /** the price shown on cards; mirrors ads.price */
22
+ is_main?: boolean;
23
+ price_type?: IPriceType | null;
24
+ };
12
25
  export type IAd = {
13
26
  id?: number;
14
27
  user_id?: number;
@@ -48,6 +61,10 @@ export type IAd = {
48
61
  service?: IService | null;
49
62
  product?: IUsedProduct | null;
50
63
  adminBoundary?: IAdminBoundary | null;
64
+ /** Rent ads (motors and properties): one price per period */
65
+ rent_prices?: IAdRentPrice[] | null;
66
+ /** In a list filtered by rent_price_type_id: the rent price of that period */
67
+ matched_rent_price?: IAdRentPrice | null;
51
68
  created_at?: string | Date;
52
69
  updated_at?: string | Date;
53
70
  expires_at?: string | Date | null;
@@ -75,6 +92,10 @@ export type GetAdsFilterDto = {
75
92
  currency_id?: number;
76
93
  min_price?: number;
77
94
  max_price?: number;
95
+ /** Rent period (price type id): price range and price sorting then use that period's price */
96
+ rent_price_type_id?: number;
97
+ sort_by?: "price" | "created_at" | "views";
98
+ sort_order?: "ASC" | "DESC";
78
99
  parent_category_id?: number;
79
100
  property?: {
80
101
  property_type_id?: number;
@@ -4,4 +4,6 @@ export type IPriceType = {
4
4
  name: LocalizedName;
5
5
  display_order?: number;
6
6
  offer_type_id?: number;
7
+ /** Length of the period in days (Daily 1, Monthly 30, Yearly 365); null for non rent types */
8
+ period_days?: number | null;
7
9
  };
@@ -13,6 +13,9 @@ export type MetaDataType = {
13
13
  maxPriceProperties: number;
14
14
  maxPriceMotors: number;
15
15
  maxPriceUsedProducts: number;
16
+ /** Highest price per rent period (price type id → base price) */
17
+ maxRentPricesProperties?: Record<number, number>;
18
+ maxRentPricesMotors?: Record<number, number>;
16
19
  };
17
20
  };
18
21
  export type MetaDataDefaults = {
@@ -41,8 +41,37 @@ export type IChatMessage = {
41
41
  export declare enum MessageTypeEnum {
42
42
  TEXT = 1,
43
43
  PRICE_OFFER = 2,
44
- CONTACT_INVITATION = 3
44
+ CONTACT_INVITATION = 3,
45
+ RENT_OFFER = 4
45
46
  }
47
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
48
+ export declare enum OfferStatusEnum {
49
+ PENDING = "pending",
50
+ ACCEPTED = "accepted",
51
+ COUNTERED = "countered",
52
+ DECLINED = "declined"
53
+ }
54
+ export type OfferSide = {
55
+ offered_price: number;
56
+ currency_id?: number;
57
+ };
58
+ /** Payload of PRICE_OFFER messages (sale offers). status and reply_to_message_id are optional for old messages. */
59
+ export type PriceOfferPayload = {
60
+ provider: OfferSide;
61
+ requester: OfferSide;
62
+ status?: OfferStatusEnum;
63
+ reply_to_message_id?: number | null;
64
+ note?: string | null;
65
+ };
66
+ /** Payload of RENT_OFFER messages: prices are per period (price_type_id). */
67
+ export type RentOfferPayload = PriceOfferPayload & {
68
+ price_type_id: number;
69
+ currency_id: number;
70
+ /** number of periods, e.g. 3 (months) */
71
+ duration: number;
72
+ /** YYYY-MM-DD */
73
+ start_date?: string | null;
74
+ };
46
75
  export { MessageTypeEnum as MessageTypeType };
47
76
  export type ContactInvitationPayload = {
48
77
  version: 1;
@@ -1,9 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MessageTypeType = exports.MessageTypeEnum = void 0;
3
+ exports.MessageTypeType = exports.OfferStatusEnum = exports.MessageTypeEnum = void 0;
4
4
  var MessageTypeEnum;
5
5
  (function (MessageTypeEnum) {
6
6
  MessageTypeEnum[MessageTypeEnum["TEXT"] = 1] = "TEXT";
7
7
  MessageTypeEnum[MessageTypeEnum["PRICE_OFFER"] = 2] = "PRICE_OFFER";
8
8
  MessageTypeEnum[MessageTypeEnum["CONTACT_INVITATION"] = 3] = "CONTACT_INVITATION";
9
+ MessageTypeEnum[MessageTypeEnum["RENT_OFFER"] = 4] = "RENT_OFFER";
9
10
  })(MessageTypeEnum || (exports.MessageTypeType = exports.MessageTypeEnum = MessageTypeEnum = {}));
11
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
12
+ var OfferStatusEnum;
13
+ (function (OfferStatusEnum) {
14
+ OfferStatusEnum["PENDING"] = "pending";
15
+ OfferStatusEnum["ACCEPTED"] = "accepted";
16
+ OfferStatusEnum["COUNTERED"] = "countered";
17
+ OfferStatusEnum["DECLINED"] = "declined";
18
+ })(OfferStatusEnum || (exports.OfferStatusEnum = OfferStatusEnum = {}));
@@ -1,12 +1,21 @@
1
+ /** google = Gemini, anthropic = Claude */
2
+ export type AiProvider = 'gemini' | 'anthropic';
3
+ /** priority = first available key in order; balanced = least tokens used today */
4
+ export type AiKeySelectionMode = 'priority' | 'balanced';
5
+ export type IAiKeySettings = {
6
+ selection_mode: AiKeySelectionMode;
7
+ };
1
8
  /**
2
- * A Gemini API key as the dashboard sees it. The key itself never leaves backend-core in this
9
+ * An AI API key (Gemini or Claude) as the dashboard sees it. The key itself never leaves backend-core in this
3
10
  * shape: only `api_key_masked` is sent.
4
11
  */
5
12
  export type IGeminiApiKey = {
6
13
  id: number;
7
14
  name: string;
15
+ provider: AiProvider;
8
16
  /** e.g. "AIza…f3Kq" */
9
17
  api_key_masked: string;
18
+ /** Default model; a service may ask for another model of the same provider */
10
19
  model_name: string;
11
20
  /** Dashboard order, starting at 1 */
12
21
  sort_order: number;
@@ -17,7 +26,7 @@ export type IGeminiApiKey = {
17
26
  daily_tokens: number;
18
27
  /** The day daily_tokens belongs to (YYYY-MM-DD) */
19
28
  daily_usage_date: string | null;
20
- /** The key is skipped until this time, after Gemini rejected it for quota */
29
+ /** The key is skipped until this time, after the provider rejected it (quota, overload, bad key) */
21
30
  cooldown_until: string | null;
22
31
  last_used_at: string | null;
23
32
  last_error: string | null;
@@ -2,3 +2,4 @@ export * from './converter';
2
2
  export * from './routes';
3
3
  export * from './adMedia';
4
4
  export * from './analysis';
5
+ export * from './rentPrices';
@@ -18,3 +18,4 @@ __exportStar(require("./converter"), exports);
18
18
  __exportStar(require("./routes"), exports);
19
19
  __exportStar(require("./adMedia"), exports);
20
20
  __exportStar(require("./analysis"), exports);
21
+ __exportStar(require("./rentPrices"), exports);
@@ -0,0 +1,8 @@
1
+ import type { IAd, IAdRentPrice } from "../types/ad/Ad";
2
+ /** The rent price shown on cards: the one marked main, else the first listed. */
3
+ export declare const getMainRentPrice: (ad?: Pick<IAd, "rent_prices"> | null) => IAdRentPrice | null;
4
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
5
+ export declare const getDisplayRentPrice: (ad?: Pick<IAd, "rent_prices" | "matched_rent_price"> | null, priceTypeId?: number | null) => IAdRentPrice | null;
6
+ /** Rent prices ordered from the shortest period to the longest. */
7
+ export declare const sortRentPrices: (prices?: IAdRentPrice[] | null) => IAdRentPrice[];
8
+ export declare const rentOfferTotal: (pricePerPeriod: number, duration: number) => number;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rentOfferTotal = exports.sortRentPrices = exports.getDisplayRentPrice = exports.getMainRentPrice = void 0;
4
+ /** The rent price shown on cards: the one marked main, else the first listed. */
5
+ const getMainRentPrice = (ad) => {
6
+ var _a, _b, _c;
7
+ const prices = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) !== null && _a !== void 0 ? _a : [];
8
+ return (_c = (_b = prices.find((item) => item.is_main)) !== null && _b !== void 0 ? _b : prices[0]) !== null && _c !== void 0 ? _c : null;
9
+ };
10
+ exports.getMainRentPrice = getMainRentPrice;
11
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
12
+ const getDisplayRentPrice = (ad, priceTypeId) => {
13
+ var _a;
14
+ if (ad === null || ad === void 0 ? void 0 : ad.matched_rent_price)
15
+ return ad.matched_rent_price;
16
+ if (priceTypeId) {
17
+ const match = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) === null || _a === void 0 ? void 0 : _a.find((item) => item.price_type_id === Number(priceTypeId));
18
+ if (match)
19
+ return match;
20
+ }
21
+ return (0, exports.getMainRentPrice)(ad);
22
+ };
23
+ exports.getDisplayRentPrice = getDisplayRentPrice;
24
+ /** Rent prices ordered from the shortest period to the longest. */
25
+ const sortRentPrices = (prices) => [...(prices !== null && prices !== void 0 ? prices : [])].sort((a, b) => {
26
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27
+ return ((_b = (_a = a.price_type) === null || _a === void 0 ? void 0 : _a.period_days) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) -
28
+ ((_d = (_c = b.price_type) === null || _c === void 0 ? void 0 : _c.period_days) !== null && _d !== void 0 ? _d : Number.MAX_SAFE_INTEGER) ||
29
+ ((_f = (_e = a.price_type) === null || _e === void 0 ? void 0 : _e.display_order) !== null && _f !== void 0 ? _f : 0) - ((_h = (_g = b.price_type) === null || _g === void 0 ? void 0 : _g.display_order) !== null && _h !== void 0 ? _h : 0);
30
+ });
31
+ exports.sortRentPrices = sortRentPrices;
32
+ const rentOfferTotal = (pricePerPeriod, duration) => Math.max(0, Number(pricePerPeriod) || 0) * Math.max(0, Math.floor(Number(duration) || 0));
33
+ exports.rentOfferTotal = rentOfferTotal;
@@ -135,6 +135,7 @@ export const APP_ROUTES = {
135
135
  by_whatsapp: props.by_whatsapp,
136
136
  phone: props.phone,
137
137
  ad_price: props.ad_price,
138
+ rent_prices: props.rent_prices,
138
139
  },
139
140
  }),
140
141
  },
@@ -163,6 +163,26 @@ export const adMediaSchema = z
163
163
  })
164
164
  .optional()
165
165
  .nullable();
166
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
167
+ export const adRentPriceSchema = z.object({
168
+ price_type_id: z.coerce.number().int().positive(),
169
+ price: z.coerce.number().positive(),
170
+ is_main: z.coerce.boolean().optional(),
171
+ });
172
+ export const adRentPricesSchema = z
173
+ .array(adRentPriceSchema)
174
+ .max(10)
175
+ .superRefine((prices, ctx) => {
176
+ const ids = prices.map((item) => item.price_type_id);
177
+ if (new Set(ids).size !== ids.length) {
178
+ ctx.addIssue({ code: "custom", message: "Each rent period can be listed once" });
179
+ }
180
+ if (prices.filter((item) => item.is_main).length > 1) {
181
+ ctx.addIssue({ code: "custom", message: "Only one rent price can be the main price" });
182
+ }
183
+ })
184
+ .optional()
185
+ .nullable();
166
186
  const baseAdCommon = z.object({
167
187
  title: z.string().trim().optional(),
168
188
  status_id: z.coerce.number().int().positive().optional(),
@@ -194,6 +214,7 @@ const baseAdCommon = z.object({
194
214
  company_id: z.coerce.number().int().positive().optional().nullable(),
195
215
  expires_at: z.iso.datetime().optional().nullable(),
196
216
  adLocations: z.array(adLocationSchema).optional().nullable(),
217
+ rent_prices: adRentPricesSchema,
197
218
  });
198
219
  export const createAdSchema = z.object({
199
220
  user_id: z.coerce.number().int().positive(),
@@ -1,15 +1,30 @@
1
1
  import { z } from 'zod';
2
2
  /**
3
- * Gemini API keys are stored in backend-core (one table) and shared by every Deallony service.
4
- * The dashboard manages them through the admin schemas; other services lease a key and report
5
- * the tokens they used through the internal schemas.
3
+ * AI API keys (Gemini and Claude) are stored in backend-core (one table) and shared by every
4
+ * Deallony service. The dashboard manages them through the admin schemas; other services lease a
5
+ * key and report the tokens they used through the internal schemas.
6
6
  */
7
7
  const geminiApiKeyIdSchema = z.coerce.number().int().positive();
8
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
8
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
9
+ export const AI_PROVIDERS = ['gemini', 'anthropic'];
10
+ export const aiProviderSchema = z.enum(AI_PROVIDERS);
11
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
12
+ export const AI_PROVIDER_MODELS = {
13
+ gemini: ['gemini-3.5-flash-lite', 'gemini-3.1-flash-lite'],
14
+ anthropic: ['claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5'],
15
+ };
16
+ /**
17
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
18
+ * order, `balanced` takes the key with the fewest tokens used today.
19
+ */
20
+ export const AI_KEY_SELECTION_MODES = ['priority', 'balanced'];
21
+ export const aiKeySelectionModeSchema = z.enum(AI_KEY_SELECTION_MODES);
22
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
9
23
  export const geminiModelNameSchema = z.string().trim().min(1).max(100);
10
24
  // ---------- Admin (dashboard) ----------
11
25
  export const createGeminiApiKeySchema = z.object({
12
26
  name: z.string().trim().min(1).max(255),
27
+ provider: aiProviderSchema.default('gemini'),
13
28
  api_key: z.string().trim().min(20).max(512),
14
29
  model_name: geminiModelNameSchema,
15
30
  is_active: z.boolean().default(true),
@@ -18,6 +33,7 @@ export const createGeminiApiKeySchema = z.object({
18
33
  export const updateGeminiApiKeySchema = z.object({
19
34
  id: geminiApiKeyIdSchema,
20
35
  name: z.string().trim().min(1).max(255).optional(),
36
+ provider: aiProviderSchema.optional(),
21
37
  api_key: z.string().trim().min(20).max(512).optional(),
22
38
  model_name: geminiModelNameSchema.optional(),
23
39
  is_active: z.boolean().optional(),
@@ -29,23 +45,34 @@ export const geminiApiKeyIdBodySchema = z.object({
29
45
  export const reorderGeminiApiKeysSchema = z.object({
30
46
  ids: z.array(geminiApiKeyIdSchema).min(1),
31
47
  });
48
+ export const aiKeySettingsSchema = z.object({
49
+ selection_mode: aiKeySelectionModeSchema,
50
+ });
32
51
  // ---------- Internal (other services) ----------
33
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
52
+ /**
53
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
54
+ * this call, so a retry moves on to the next key in the order.
55
+ */
34
56
  export const leaseGeminiApiKeySchema = z.object({
57
+ /** Only keys whose default model is this one */
35
58
  model_name: geminiModelNameSchema.optional(),
59
+ /** Only keys of these providers; all providers when left out */
60
+ providers: z.array(aiProviderSchema).min(1).optional(),
61
+ exclude_ids: z.array(geminiApiKeyIdSchema).optional(),
36
62
  });
37
63
  export const leasedGeminiApiKeySchema = z.object({
38
64
  id: z.number().int().positive(),
65
+ provider: aiProviderSchema,
39
66
  api_key: z.string(),
40
67
  model_name: z.string(),
41
68
  });
42
- /** Sent after every Gemini call made with a leased key. */
69
+ /** Sent after every AI call made with a leased key. */
43
70
  export const reportGeminiApiKeyUsageSchema = z.object({
44
71
  id: geminiApiKeyIdSchema,
45
- /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
72
+ /** Total tokens of the response (input + output); 0 when the call failed */
46
73
  total_tokens: z.coerce.number().int().min(0).default(0),
47
74
  success: z.boolean().default(true),
48
- /** HTTP status Gemini answered with, e.g. 429 */
75
+ /** HTTP status the provider answered with, e.g. 429 */
49
76
  error_code: z.coerce.number().int().optional(),
50
77
  error_message: z.string().trim().max(2000).optional(),
51
78
  });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-conversation.schema.js';
2
2
  export * from './support-ticket.schema.js';
3
+ export * from './offer.schema.js';
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ export const offerStatusSchema = z.enum(["pending", "accepted", "countered", "declined"]);
3
+ // The seller's side can be 0 when the ad has no price; the backend requires a positive buyer price
4
+ const offerSideSchema = z.object({
5
+ offered_price: z.coerce.number().min(0),
6
+ currency_id: z.coerce.number().int().positive().optional(),
7
+ });
8
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
9
+ export const priceOfferPayloadSchema = z.object({
10
+ provider: offerSideSchema,
11
+ requester: offerSideSchema,
12
+ status: offerStatusSchema.optional(),
13
+ reply_to_message_id: z.coerce.number().int().positive().optional().nullable(),
14
+ /** Free text the sender added; the message text itself is a readable summary for older apps */
15
+ note: z.string().trim().max(1000).optional().nullable(),
16
+ });
17
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
18
+ export const rentOfferPayloadSchema = priceOfferPayloadSchema.extend({
19
+ price_type_id: z.coerce.number().int().positive(),
20
+ currency_id: z.coerce.number().int().positive(),
21
+ duration: z.coerce.number().int().min(1).max(1000),
22
+ start_date: z
23
+ .string()
24
+ .trim()
25
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
26
+ .optional()
27
+ .nullable(),
28
+ });
@@ -3,5 +3,14 @@ export var MessageTypeEnum;
3
3
  MessageTypeEnum[MessageTypeEnum["TEXT"] = 1] = "TEXT";
4
4
  MessageTypeEnum[MessageTypeEnum["PRICE_OFFER"] = 2] = "PRICE_OFFER";
5
5
  MessageTypeEnum[MessageTypeEnum["CONTACT_INVITATION"] = 3] = "CONTACT_INVITATION";
6
+ MessageTypeEnum[MessageTypeEnum["RENT_OFFER"] = 4] = "RENT_OFFER";
6
7
  })(MessageTypeEnum || (MessageTypeEnum = {}));
8
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
9
+ export var OfferStatusEnum;
10
+ (function (OfferStatusEnum) {
11
+ OfferStatusEnum["PENDING"] = "pending";
12
+ OfferStatusEnum["ACCEPTED"] = "accepted";
13
+ OfferStatusEnum["COUNTERED"] = "countered";
14
+ OfferStatusEnum["DECLINED"] = "declined";
15
+ })(OfferStatusEnum || (OfferStatusEnum = {}));
7
16
  export { MessageTypeEnum as MessageTypeType };
@@ -2,3 +2,4 @@ export * from './converter.js';
2
2
  export * from './routes.js';
3
3
  export * from './adMedia.js';
4
4
  export * from './analysis.js';
5
+ export * from './rentPrices.js';
@@ -0,0 +1,26 @@
1
+ /** The rent price shown on cards: the one marked main, else the first listed. */
2
+ export const getMainRentPrice = (ad) => {
3
+ var _a, _b, _c;
4
+ const prices = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) !== null && _a !== void 0 ? _a : [];
5
+ return (_c = (_b = prices.find((item) => item.is_main)) !== null && _b !== void 0 ? _b : prices[0]) !== null && _c !== void 0 ? _c : null;
6
+ };
7
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
8
+ export const getDisplayRentPrice = (ad, priceTypeId) => {
9
+ var _a;
10
+ if (ad === null || ad === void 0 ? void 0 : ad.matched_rent_price)
11
+ return ad.matched_rent_price;
12
+ if (priceTypeId) {
13
+ const match = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) === null || _a === void 0 ? void 0 : _a.find((item) => item.price_type_id === Number(priceTypeId));
14
+ if (match)
15
+ return match;
16
+ }
17
+ return getMainRentPrice(ad);
18
+ };
19
+ /** Rent prices ordered from the shortest period to the longest. */
20
+ export const sortRentPrices = (prices) => [...(prices !== null && prices !== void 0 ? prices : [])].sort((a, b) => {
21
+ var _a, _b, _c, _d, _e, _f, _g, _h;
22
+ return ((_b = (_a = a.price_type) === null || _a === void 0 ? void 0 : _a.period_days) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) -
23
+ ((_d = (_c = b.price_type) === null || _c === void 0 ? void 0 : _c.period_days) !== null && _d !== void 0 ? _d : Number.MAX_SAFE_INTEGER) ||
24
+ ((_f = (_e = a.price_type) === null || _e === void 0 ? void 0 : _e.display_order) !== null && _f !== void 0 ? _f : 0) - ((_h = (_g = b.price_type) === null || _g === void 0 ? void 0 : _g.display_order) !== null && _h !== void 0 ? _h : 0);
25
+ });
26
+ export const rentOfferTotal = (pricePerPeriod, duration) => Math.max(0, Number(pricePerPeriod) || 0) * Math.max(0, Math.floor(Number(duration) || 0));
package/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@deallony/shared",
3
- "version": "1.2.67",
4
- "private": false,
5
- "main": "dist/cjs/index.js",
6
- "module": "dist/esm/index.js",
7
- "react-native": "dist/esm/index.js",
8
- "types": "dist/cjs/index.d.ts",
9
- "sideEffects": false,
10
- "exports": {
11
- ".": {
12
- "require": "./dist/cjs/index.js",
13
- "import": "./dist/esm/index.js",
14
- "types": "./dist/cjs/index.d.ts"
15
- }
16
- },
17
- "files": [
18
- "dist"
19
- ],
20
- "scripts": {
21
- "deploy": "node ./scripts/publish.js npm patch",
22
- "=========": "============= UPDATE APPS LIBRARY =============",
23
- "all:update": "npm run mobile:update && npm run backend:update && npm run dashboard:update && npm run whatsapp:update && npm run collect:update",
24
- "mobile:update": "cd ../app && npm run upgrade:deallony:shared",
25
- "backend:update": "cd ../backend-service-marketplace && pnpm run upgrade:deallony:shared",
26
- "dashboard:update": "cd ../frontend-react-aloo && pnpm run upgrade:deallony:shared",
27
- "whatsapp:update": "cd ../backend-nest-whatsapp-service-aloo && pnpm run upgrade:deallony:shared",
28
- "collect:update": "cd ../qatayef-collect/backend && npm run upgrade:deallony:shared",
29
- "current": "node -p \"require('./package.json').version\"",
30
- "============================= Links with project =============================": "",
31
- "link:backend": "cd ../backend-service-marketplace && pnpm add @deallony/shared@file:../shared-deallony",
32
- "link:dashboard": "cd ../frontend-react-aloo && pnpm add @deallony/shared@file:../shared-deallony",
33
- "link:mobile": "cd ../app && npm install ../shared-deallony",
34
- "========": "============ DEFAULT ============",
35
- "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node ./scripts/fix-esm-imports.mjs",
36
- "watch": "tsc -p tsconfig.cjs.json --watch",
37
- "deploy:patch": "node ./scripts/publish.js npm patch",
38
- "deploy:minor": "node ./scripts/publish.js npm minor",
39
- "deploy:major": "node ./scripts/publish.js npm major",
40
- "deploy:dry-run": "node ./scripts/publish.js npm patch --dry-run",
41
- "deploy:git": "node ./scripts/publish.js git patch",
42
- "deploy:git:minor": "node ./scripts/publish.js git minor",
43
- "deploy:git:major": "node ./scripts/publish.js git major",
44
- "deploy:git:dry-run": "node ./scripts/publish.js git patch --dry-run",
45
- "prepublishOnly": "npm run build"
46
- },
47
- "dependencies": {
48
- "zod": "^4.2.1"
49
- },
50
- "devDependencies": {
51
- "@types/node": "^25.0.3",
52
- "typescript": "^5.9.3"
53
- },
54
- "publishConfig": {
55
- "registry": "https://registry.npmjs.org/",
56
- "access": "public"
57
- }
58
- }
1
+ {
2
+ "name": "@deallony/shared",
3
+ "version": "1.2.69",
4
+ "private": false,
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "react-native": "dist/esm/index.js",
8
+ "types": "dist/cjs/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "require": "./dist/cjs/index.js",
13
+ "import": "./dist/esm/index.js",
14
+ "types": "./dist/cjs/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "deploy": "node ./scripts/publish.js npm patch",
22
+ "=========": "============= UPDATE APPS LIBRARY =============",
23
+ "all:update": "npm run mobile:update && npm run backend:update && npm run dashboard:update && npm run whatsapp:update && npm run collect:update",
24
+ "mobile:update": "cd ../app && npm run upgrade:deallony:shared",
25
+ "backend:update": "cd ../backend-service-marketplace && pnpm run upgrade:deallony:shared",
26
+ "dashboard:update": "cd ../frontend-react-aloo && pnpm run upgrade:deallony:shared",
27
+ "whatsapp:update": "cd ../backend-nest-whatsapp-service-aloo && pnpm run upgrade:deallony:shared",
28
+ "collect:update": "cd ../qatayef-collect/backend && npm run upgrade:deallony:shared",
29
+ "current": "node -p \"require('./package.json').version\"",
30
+ "============================= Links with project =============================": "",
31
+ "link:backend": "cd ../backend-service-marketplace && pnpm add @deallony/shared@file:../shared-deallony",
32
+ "link:dashboard": "cd ../frontend-react-aloo && pnpm add @deallony/shared@file:../shared-deallony",
33
+ "link:mobile": "cd ../app && npm install ../shared-deallony",
34
+ "========": "============ DEFAULT ============",
35
+ "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node ./scripts/fix-esm-imports.mjs",
36
+ "watch": "tsc -p tsconfig.cjs.json --watch",
37
+ "deploy:patch": "node ./scripts/publish.js npm patch",
38
+ "deploy:minor": "node ./scripts/publish.js npm minor",
39
+ "deploy:major": "node ./scripts/publish.js npm major",
40
+ "deploy:dry-run": "node ./scripts/publish.js npm patch --dry-run",
41
+ "deploy:git": "node ./scripts/publish.js git patch",
42
+ "deploy:git:minor": "node ./scripts/publish.js git minor",
43
+ "deploy:git:major": "node ./scripts/publish.js git major",
44
+ "deploy:git:dry-run": "node ./scripts/publish.js git patch --dry-run",
45
+ "prepublishOnly": "npm run build"
46
+ },
47
+ "dependencies": {
48
+ "zod": "^4.2.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.0.3",
52
+ "typescript": "^5.9.3"
53
+ },
54
+ "publishConfig": {
55
+ "registry": "https://registry.npmjs.org/",
56
+ "access": "public"
57
+ }
58
+ }