@blackcode_sa/metaestetics-api 1.12.32 → 1.12.34

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.
@@ -76,8 +76,12 @@ export interface ProcedureExtendedInfo {
76
76
  procedureTechnologyName: string;
77
77
  procedureProductBrandId: string;
78
78
  procedureProductBrandName: string;
79
- procedureProductId: string; // We need to have more than one product allowed in this data, this is coming from aggregation, so please update aggregation and this data type
80
- procedureProductName: string; // We need to have more than one product allowed in this data, this is coming from aggregation, so please update aggregation and this data type
79
+ procedureProducts: Array<{
80
+ productId: string;
81
+ productName: string;
82
+ brandId: string;
83
+ brandName: string;
84
+ }>;
81
85
  }
82
86
 
83
87
  /**
@@ -132,26 +136,39 @@ export interface ZonePhotoUploadData {
132
136
  }
133
137
 
134
138
  /**
135
- * Interface for billing information per zone
139
+ * Interface for zone item data (products or notes per zone)
136
140
  */
137
- export interface BillingPerZone { // We should rename this to ZoneItemData and we should expend it as per our documentations (will be used for both zone items in step 3 and summary and pricing in the step 4)
138
- /** Product name/description */
141
+ export interface ZoneItemData {
142
+ productId?: string;
143
+ productName?: string;
144
+ productBrandId?: string;
145
+ productBrandName?: string;
146
+ belongingProcedureId: string;
147
+ type: 'item' | 'note';
148
+ price?: number;
149
+ currency?: Currency;
150
+ unitOfMeasurement?: PricingMeasure;
151
+ priceOverrideAmount?: number; // If set, takes precedence over price
152
+ quantity?: number;
153
+ parentZone: string; // Zone key in format "category.zone" (e.g., "face.forehead")
154
+ subzones: string[];
155
+ notes?: string;
156
+ subtotal?: number;
157
+ ionNumber?: string;
158
+ }
159
+
160
+ /**
161
+ * @deprecated Use ZoneItemData instead
162
+ */
163
+ export interface BillingPerZone {
139
164
  Product: string;
140
- /** Product ID */
141
165
  ProductId: string | null;
142
- /** Quantity used (can be decimal) */
143
166
  Quantity: number;
144
- /** Unit of measurement */
145
167
  UnitOfMeasurement: PricingMeasure;
146
- /** Unit price for the product */
147
168
  UnitPrice: number;
148
- /** Currency for the unit price */
149
169
  UnitCurency: Currency;
150
- /** Calculated subtotal */
151
170
  Subtotal: number;
152
- /** Optional billing note */
153
171
  Note: string | null;
154
- /** Ion/Batch number for traceability */
155
172
  IonNumber: string | null;
156
173
  }
157
174
 
@@ -167,29 +184,61 @@ export interface FinalBilling {
167
184
  taxPrice: number;
168
185
  /** Final price including tax */
169
186
  finalPrice: number;
170
- /** Total final quantity across all zones */
171
- finalQuantity: number; // Not sure we should keep this as well, we keep track of this per item
172
187
  /** Currency for the final billing */
173
188
  currency: Currency;
174
- /** Unit of measurement for the final billing */
175
- unitOfMeasurement: PricingMeasure; // NO need to keep this, we keep track of this per item
189
+ }
190
+
191
+ /**
192
+ * Interface for product metadata in appointment
193
+ */
194
+ export interface AppointmentProductMetadata {
195
+ productId: string;
196
+ productName: string;
197
+ brandId: string;
198
+ brandName: string;
199
+ procedureId: string;
200
+ price: number;
201
+ currency: Currency;
202
+ unitOfMeasurement: PricingMeasure;
203
+ }
204
+
205
+ /**
206
+ * Interface for extended procedures in appointment
207
+ */
208
+ export interface ExtendedProcedureInfo {
209
+ procedureId: string;
210
+ procedureName: string;
211
+ procedureFamily?: ProcedureFamily;
212
+ procedureCategoryId: string;
213
+ procedureCategoryName: string;
214
+ procedureSubCategoryId: string;
215
+ procedureSubCategoryName: string;
216
+ procedureTechnologyId: string;
217
+ procedureTechnologyName: string;
218
+ procedureProducts: Array<{
219
+ productId: string;
220
+ productName: string;
221
+ brandId: string;
222
+ brandName: string;
223
+ }>;
176
224
  }
177
225
 
178
226
  /**
179
227
  * Interface for appointment metadata containing zone-specific information
180
228
  */
181
229
  export interface AppointmentMetadata {
182
- /** Array of selected zones for the appointment */
183
- selectedZones: string[] | null; // We are not going to accept any structure (category.zone.subzone), here we will only accept zone (category.zone), nothing more, nothing less
184
- /** Map of zone photos with before/after images and notes */
185
- zonePhotos: Record<string, BeforeAfterPerZone> | null; // head.forhead: {object}, head: {object}, but it can't have head.forhead.forhedLeft: {object}
186
- /** Map of billing information per zone */
187
- zoneBilling: Record<string, BillingPerZone> | null; // This is going to change, name it zonesData, and expand this to accept more than one item per key (which is basically zone like category.zone)
188
- /** Final billing calculations for the appointment */
230
+ selectedZones: string[] | null;
231
+ zonePhotos: Record<string, BeforeAfterPerZone> | null;
232
+ zonesData?: Record<string, ZoneItemData[]> | null;
233
+ appointmentProducts?: AppointmentProductMetadata[];
234
+ extendedProcedures?: ExtendedProcedureInfo[];
189
235
  finalbilling: FinalBilling | null;
190
- /** Final note for the appointment */
191
236
  finalizationNotes: string | null;
192
- // TO-DO: We need to add extended procedures info (when we add multiple procedures, different than default one defined above)
237
+
238
+ /**
239
+ * @deprecated Use zonesData instead
240
+ */
241
+ zoneBilling?: Record<string, BillingPerZone> | null;
193
242
  }
194
243
 
195
244
  /**
@@ -53,8 +53,14 @@ export const procedureExtendedInfoSchema = z.object({
53
53
  procedureTechnologyName: z.string(),
54
54
  procedureProductBrandId: z.string(),
55
55
  procedureProductBrandName: z.string(),
56
- procedureProductId: z.string(),
57
- procedureProductName: z.string(),
56
+ procedureProducts: z.array(
57
+ z.object({
58
+ productId: z.string().min(MIN_STRING_LENGTH, 'Product ID is required'),
59
+ productName: z.string().min(MIN_STRING_LENGTH, 'Product name is required'),
60
+ brandId: z.string().min(MIN_STRING_LENGTH, 'Brand ID is required'),
61
+ brandName: z.string().min(MIN_STRING_LENGTH, 'Brand name is required'),
62
+ })
63
+ ),
58
64
  });
59
65
 
60
66
  export const linkedFormInfoSchema = z.object({
@@ -164,18 +170,108 @@ export const finalBillingSchema = z.object({
164
170
  taxRate: z.number().min(0).max(1, 'Tax rate must be between 0 and 1'),
165
171
  taxPrice: z.number().min(0, 'Tax price must be non-negative'),
166
172
  finalPrice: z.number().min(0, 'Final price must be non-negative'),
167
- finalQuantity: z.number().min(0, 'Final quantity must be non-negative'),
173
+ currency: z.nativeEnum(Currency),
174
+ });
175
+
176
+ /**
177
+ * Schema for zone item data (product or note per zone)
178
+ */
179
+ export const zoneItemDataSchema = z.object({
180
+ productId: z.string().optional(),
181
+ productName: z.string().optional(),
182
+ productBrandId: z.string().optional(),
183
+ productBrandName: z.string().optional(),
184
+ belongingProcedureId: z.string().min(MIN_STRING_LENGTH, 'Belonging procedure ID is required'),
185
+ type: z.enum(['item', 'note'], {
186
+ required_error: 'Type must be either "item" or "note"',
187
+ }),
188
+ price: z.number().min(0, 'Price must be non-negative').optional(),
189
+ currency: z.nativeEnum(Currency).optional(),
190
+ unitOfMeasurement: z.nativeEnum(PricingMeasure).optional(),
191
+ priceOverrideAmount: z.number().min(0, 'Price override amount must be non-negative').optional(),
192
+ quantity: z.number().min(0, 'Quantity must be non-negative').optional(),
193
+ parentZone: z.string().min(MIN_STRING_LENGTH, 'Parent zone is required').refine(
194
+ val => {
195
+ const parts = val.split('.');
196
+ return parts.length === 2;
197
+ },
198
+ {
199
+ message: 'Parent zone must be in "category.zone" format (e.g., "face.forehead")',
200
+ }
201
+ ),
202
+ subzones: z.array(z.string()).refine(
203
+ val => {
204
+ return val.every(subzone => {
205
+ const parts = subzone.split('.');
206
+ return parts.length === 3;
207
+ });
208
+ },
209
+ {
210
+ message: 'Subzones must be in "category.zone.subzone" format (e.g., "face.forehead.left")',
211
+ }
212
+ ),
213
+ notes: z.string().max(MAX_STRING_LENGTH_LONG, 'Notes too long').optional(),
214
+ subtotal: z.number().min(0, 'Subtotal must be non-negative').optional(),
215
+ ionNumber: z.string().optional(),
216
+ }).refine(
217
+ data => {
218
+ if (data.type === 'item') {
219
+ return !!(data.productId && data.productName && (data.price || data.priceOverrideAmount));
220
+ }
221
+ return true;
222
+ },
223
+ {
224
+ message: 'Item type requires productId, productName, and either price or priceOverrideAmount',
225
+ }
226
+ );
227
+
228
+ /**
229
+ * Schema for appointment product metadata
230
+ */
231
+ export const appointmentProductMetadataSchema = z.object({
232
+ productId: z.string().min(MIN_STRING_LENGTH, 'Product ID is required'),
233
+ productName: z.string().min(MIN_STRING_LENGTH, 'Product name is required'),
234
+ brandId: z.string().min(MIN_STRING_LENGTH, 'Brand ID is required'),
235
+ brandName: z.string().min(MIN_STRING_LENGTH, 'Brand name is required'),
236
+ procedureId: z.string().min(MIN_STRING_LENGTH, 'Procedure ID is required'),
237
+ price: z.number().min(0, 'Price must be non-negative'),
168
238
  currency: z.nativeEnum(Currency),
169
239
  unitOfMeasurement: z.nativeEnum(PricingMeasure),
170
240
  });
171
241
 
242
+ /**
243
+ * Schema for extended procedure info
244
+ */
245
+ export const extendedProcedureInfoSchema = z.object({
246
+ procedureId: z.string().min(MIN_STRING_LENGTH, 'Procedure ID is required'),
247
+ procedureName: z.string().min(MIN_STRING_LENGTH, 'Procedure name is required'),
248
+ procedureFamily: z.any(),
249
+ procedureCategoryId: z.string().min(MIN_STRING_LENGTH, 'Category ID is required'),
250
+ procedureCategoryName: z.string().min(MIN_STRING_LENGTH, 'Category name is required'),
251
+ procedureSubCategoryId: z.string().min(MIN_STRING_LENGTH, 'Subcategory ID is required'),
252
+ procedureSubCategoryName: z.string().min(MIN_STRING_LENGTH, 'Subcategory name is required'),
253
+ procedureTechnologyId: z.string().min(MIN_STRING_LENGTH, 'Technology ID is required'),
254
+ procedureTechnologyName: z.string().min(MIN_STRING_LENGTH, 'Technology name is required'),
255
+ procedureProducts: z.array(
256
+ z.object({
257
+ productId: z.string().min(MIN_STRING_LENGTH, 'Product ID is required'),
258
+ productName: z.string().min(MIN_STRING_LENGTH, 'Product name is required'),
259
+ brandId: z.string().min(MIN_STRING_LENGTH, 'Brand ID is required'),
260
+ brandName: z.string().min(MIN_STRING_LENGTH, 'Brand name is required'),
261
+ })
262
+ ),
263
+ });
264
+
172
265
  /**
173
266
  * Schema for appointment metadata containing zone-specific information
174
267
  */
175
268
  export const appointmentMetadataSchema = z.object({
176
269
  selectedZones: z.array(z.string()).nullable(),
177
270
  zonePhotos: z.record(z.string(), beforeAfterPerZoneSchema).nullable(),
178
- zoneBilling: z.record(z.string(), billingPerZoneSchema).nullable(),
271
+ zonesData: z.record(z.string(), z.array(zoneItemDataSchema)).nullable(),
272
+ appointmentProducts: z.array(appointmentProductMetadataSchema),
273
+ extendedProcedures: z.array(extendedProcedureInfoSchema),
274
+ zoneBilling: z.record(z.string(), billingPerZoneSchema).nullable().optional(),
179
275
  finalbilling: finalBillingSchema.nullable(),
180
276
  finalizationNotes: z.string().nullable(),
181
277
  });