Skip to content

Commit 8e2c97f

Browse files
sluFicodesSHENGXING LU
andauthored
TIER PRICING (#192)
* duplicate name validation implemented * added explanation about a char name duplicate validation * tier pricing front implemented * anti collision validator in frontend * add unidirectional range in product spec * validate valueFrom valueTo --------- Co-authored-by: SHENGXING LU <slu@SHENGXINGs-MacBook-Air.local>
1 parent c9ede39 commit 8e2c97f

11 files changed

Lines changed: 1086 additions & 35 deletions

File tree

src/app/pages/seller-offerings/offerings/seller-product-spec/create-product-spec/create-product-spec.component.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,17 @@ export class CreateProductSpecComponent implements OnInit, OnDestroy {
11341134
this.numberValue='';
11351135
}else if(this.rangeCharSelected){
11361136
console.log('range')
1137+
// Validate that fromValue < toValue
1138+
const fromVal = Number(this.fromValue);
1139+
const toVal = Number(this.toValue);
1140+
if (fromVal >= toVal) {
1141+
console.log('range validation error: valueFrom >= valueTo')
1142+
this.errorMessage = 'Invalid range: "From" value must be less than "To" value';
1143+
this.showError = true;
1144+
setTimeout(() => {this.showError = false}, 3000);
1145+
return;
1146+
}
1147+
11371148
if(this.creatingChars.length==0){
11381149
this.creatingChars.push({
11391150
isDefault:true,
@@ -1147,22 +1158,10 @@ export class CreateProductSpecComponent implements OnInit, OnDestroy {
11471158
valueFrom:this.fromValue as any,
11481159
valueTo:this.toValue as any,
11491160
unitOfMeasure:this.rangeUnit})
1150-
}
1151-
} else {
1152-
console.log('boolean')
1153-
if(this.creatingChars.length==0){
1154-
this.creatingChars.push({
1155-
isDefault:true,
1156-
value:this.booleanValue as any
1157-
})
1158-
} else{
1159-
this.creatingChars.push({
1160-
isDefault:false,
1161-
value:this.booleanValue as any
1162-
})
11631161
}
1162+
} else {
1163+
console.log('nothing')
11641164
}
1165-
this.booleanValue=false;
11661165
}
11671166

11681167
removeCharValue(char:any,idx:any){
@@ -1183,6 +1182,17 @@ export class CreateProductSpecComponent implements OnInit, OnDestroy {
11831182

11841183
saveChar(){
11851184
if(this.charsForm.value.name!=null){
1185+
1186+
// In showFinish() only takes the first ocurrence in name for sending to proxy
1187+
// I validate the duplication here to prevent confusion in client when suddenly a characteristic with the same name dissapeared
1188+
if (this.prodChars.find((char)=> char.name === this.charsForm.value.name)){
1189+
console.log('name duplicated error')
1190+
this.errorMessage = 'Cannot save duplicated name in characteristics';
1191+
this.showError = true;
1192+
setTimeout(() => {this.showError = false}, 3000);
1193+
return
1194+
}
1195+
11861196
// Create the main characteristic
11871197
this.prodChars.push({
11881198
id: 'urn:ngsi-ld:characteristic:'+uuidv4(),

src/app/pages/seller-offerings/offerings/seller-product-spec/update-product-spec/update-product-spec.component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,17 @@ export class UpdateProductSpecComponent implements OnInit, OnDestroy {
13421342
this.numberValue='';
13431343
}else if(this.rangeCharSelected){
13441344
console.log('range')
1345+
// Validate that fromValue < toValue
1346+
const fromVal = Number(this.fromValue);
1347+
const toVal = Number(this.toValue);
1348+
if (fromVal >= toVal) {
1349+
console.log('range validation error: valueFrom >= valueTo')
1350+
this.errorMessage = 'Invalid range: "From" value must be less than "To" value';
1351+
this.showError = true;
1352+
setTimeout(() => {this.showError = false}, 3000);
1353+
return;
1354+
}
1355+
13451356
if(this.creatingChars.length==0){
13461357
this.creatingChars.push({
13471358
isDefault:true,

src/app/shared/forms/offer/price-plans/price-component-drawer/price-component-drawer.component.html

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ <h2 class="text-xl font-semibold text-gray-700 dark:text-white">
106106
</div>
107107
</div>
108108
<div class="flex flex-row">
109-
<select id="type" (change)="changePriceComponentChar($event)"
109+
<select data-cy="selectPriceSpecChar" id="type" (change)="changePriceComponentChar($event)"
110110
class="mb-2 mr-2 bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-gray-100 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
111111
@if(!selectedCharacteristic){
112112
<option value="">Select a characteristic</option>
@@ -121,20 +121,48 @@ <h2 class="text-xl font-semibold text-gray-700 dark:text-white">
121121
}
122122
}
123123
}
124-
</select>
125-
@if(showValueSelect){
126-
<select id="type" (change)="changePriceComponentCharValue($event)"
127-
class="mb-2 bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-gray-100 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
128-
@for(val of selectedCharacteristic.productSpecCharacteristicValue; track val){
129-
@if(hasKey(val, 'unitOfMeasure')){
130-
<option value="{{val.value}}">{{val.value}} ({{val.unitOfMeasure}})</option>
131-
} @else {
132-
<option value="{{val.value}}">{{val.value}}</option>
133-
}
134-
}
135-
</select>
136-
}
124+
</select>
125+
@if(showValueSelect){
126+
<select id="type" (change)="changePriceComponentCharValue($event)"
127+
class="mb-2 bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-gray-100 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
128+
@for(val of selectedCharacteristic.productSpecCharacteristicValue; track val){
129+
@if(hasKey(val, 'unitOfMeasure')){
130+
<option value="{{val.value}}">{{val.value}} ({{val.unitOfMeasure}})</option>
131+
} @else {
132+
<option value="{{val.value}}">{{val.value}}</option>
133+
}
134+
}
135+
</select>
136+
}
137137
</div>
138+
139+
@if(selectedCharacteristic && selectedCharacteristic.productSpecCharacteristicValue?.[0] &&
140+
hasKey(selectedCharacteristic.productSpecCharacteristicValue[0], 'valueFrom') &&
141+
hasKey(selectedCharacteristic.productSpecCharacteristicValue[0], 'valueTo')){
142+
<div class="bg-blue-50 dark:bg-gray-900 p-3 rounded border border-blue-200 dark:border-blue-800 mb-4">
143+
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">
144+
Selected Range: [{{ selectedCharacteristic.productSpecCharacteristicValue[0].valueFrom }},{{ selectedCharacteristic.productSpecCharacteristicValue[0].valueTo }}].
145+
Not modifiable, in case you want Tiering range prices go to price plan modal and click on Tier Pricing button
146+
</label>
147+
<div class="relative w-full h-12 bg-gray-200 dark:bg-gray-700 rounded px-4">
148+
<div class="absolute top-1/2 left-4 right-4 h-2 bg-blue-500 dark:bg-blue-400 rounded transform -translate-y-1/2"></div>
149+
150+
<div class="absolute top-1/2 left-4 transform -translate-y-1/2 -translate-x-1/2">
151+
<div class="w-3 h-3 bg-blue-600 dark:bg-blue-500 rounded-full border-2 border-white"></div>
152+
<span class="absolute top-5 left-1/2 transform -translate-x-1/2 text-xs font-semibold text-blue-700 dark:text-blue-300 whitespace-nowrap">
153+
{{ selectedCharacteristic.productSpecCharacteristicValue[0].valueFrom }}
154+
</span>
155+
</div>
156+
157+
<div class="absolute top-1/2 right-4 transform -translate-y-1/2 translate-x-1/2">
158+
<div class="w-3 h-3 bg-blue-600 dark:bg-blue-500 rounded-full border-2 border-white"></div>
159+
<span class="absolute top-5 left-1/2 transform -translate-x-1/2 text-xs font-semibold text-blue-700 dark:text-blue-300 whitespace-nowrap">
160+
{{ selectedCharacteristic.productSpecCharacteristicValue[0].valueTo }}
161+
</span>
162+
</div>
163+
</div>
164+
</div>
165+
}
138166
}
139167

140168
<!-- Tipo de Precio -->

src/app/shared/forms/offer/price-plans/price-component-drawer/price-component-drawer.component.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export class PriceComponentDrawerComponent implements OnInit {
179179
}
180180

181181
this.priceComponentForm.patchValue({
182-
selectedCharacteristic: this.mapChars(this.selectedCharacteristicVal)
182+
selectedCharacteristic: [this.mapChars(this.selectedCharacteristicVal)]
183183
});
184184
}
185185

@@ -191,12 +191,19 @@ export class PriceComponentDrawerComponent implements OnInit {
191191
description: this.selectedCharacteristic.description || '',
192192
}
193193

194-
// Add the productSpecCharacteristicValue only if needed
195-
// Range chars not include a value
194+
// Add the productSpecCharacteristicValue
196195
if (this.showValueSelect) {
196+
// For non-range characteristics (with specific values)
197197
char.productSpecCharacteristicValue = [this.selectedCharacteristic.productSpecCharacteristicValue.find((opt: any) => {
198198
return String(opt.value) === String(charValue);
199199
})];
200+
} else if (this.selectedCharacteristic.productSpecCharacteristicValue?.[0]) {
201+
// For range characteristics, include the full range
202+
char.productSpecCharacteristicValue = [{
203+
valueFrom: this.selectedCharacteristic.productSpecCharacteristicValue[0].valueFrom,
204+
valueTo: this.selectedCharacteristic.productSpecCharacteristicValue[0].valueTo,
205+
isDefault: true
206+
}];
200207
}
201208

202209
return char
@@ -205,7 +212,7 @@ export class PriceComponentDrawerComponent implements OnInit {
205212
changePriceComponentCharValue(event: any){
206213
this.selectedCharacteristicVal = event.target.value;
207214
this.priceComponentForm.patchValue({
208-
selectedCharacteristic: this.mapChars(event.target.value)
215+
selectedCharacteristic: [this.mapChars(event.target.value)]
209216
});
210217
}
211218

src/app/shared/forms/offer/price-plans/price-plan-drawer/price-plan-drawer.component.html

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[ngClass]="{'opacity-100': isOpen, 'opacity-0 pointer-events-none': !isOpen}">
44

55
<!-- Overlay when child drawer is open - covers entire viewport and dims parent -->
6-
<div *ngIf="showPriceComponentDrawer"
6+
<div *ngIf="showPriceComponentDrawer || showTierPricingDrawer"
77
class="fixed inset-0 bg-black bg-opacity-30 pointer-events-none z-[51]"></div>
88

99
<!-- Drawer Content -->
@@ -200,9 +200,9 @@ <h3 class="font-bold text-lg dark:text-white">
200200
(delete)="deletePriceComponent($event)">
201201
</app-price-components-table>
202202

203-
<div class="px-4">
203+
<div class="px-4 flex flex-wrap gap-2">
204204
<button data-cy="newPriceComponent" type="button" (click)="openPriceComponentDrawer()"
205-
class="mt-2 px-4 py-2 bg-blue-500 text-white rounded flex items-center">
205+
class="mt-2 px-4 py-2 bg-blue-500 text-white rounded flex items-center hover:bg-blue-600">
206206
<svg class="min-w-4 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
207207
<path fill="currentColor"
208208
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM232 344l0-64-64 0c-13.3 0-24-10.7-24-24s10.7-24
@@ -211,11 +211,34 @@ <h3 class="font-bold text-lg dark:text-white">
211211
</svg>
212212
{{ 'FORMS.PRICE_PLANS._add_price_component' | translate }}
213213
</button>
214+
215+
<button
216+
data-cy="newTierPricing"
217+
type="button"
218+
(click)="openTierPricingDrawer()"
219+
[disabled]="!hasRangeCharacteristics()"
220+
[ngClass]="hasRangeCharacteristics() ? 'bg-green-500 hover:bg-green-600 cursor-pointer' : 'bg-gray-400 cursor-not-allowed opacity-50'"
221+
class="mt-2 px-4 py-2 text-white rounded flex items-center"
222+
[title]="!hasRangeCharacteristics() ? 'No range characteristics available in product specification' : 'Create tier pricing based on range characteristics'"
223+
>
224+
<svg class="min-w-4 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
225+
<path fill="currentColor"
226+
d="M448 160l-128 0 0-32 128 0 0 32zM48 64C21.5 64 0 85.5 0 112l0 64c0 26.5 21.5 48 48 48l416 0c26.5 0 48-21.5 48-48l0-64c0-26.5-21.5-48-48-48L48 64zM448 352l-256 0 0-32 256 0 0 32zM48 256c-26.5 0-48 21.5-48 48l0 64c0 26.5 21.5 48 48 48l416 0c26.5 0 48-21.5 48-48l0-64c0-26.5-21.5-48-48-48L48 256z"/>
227+
</svg>
228+
{{ 'FORMS.PRICE_PLANS._add_tier_pricing' | translate }}
229+
</button>
214230
</div>
215231
</div>
216232
</div>
217233
}
218234

235+
<!-- Range Validation Error Message -->
236+
@if(rangeValidationError) {
237+
<div class="mb-4 p-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-red-900 dark:text-red-200" role="alert">
238+
<span class="font-medium">{{ rangeValidationError }}</span>
239+
</div>
240+
}
241+
219242
<!-- Action Buttons -->
220243
<div class="flex justify-end space-x-3">
221244
<button data-cy="cancelPricePlan" type="button" (click)="closeDrawer()" class="px-4 py-2 bg-gray-300 dark:bg-gray-600 rounded">
@@ -252,4 +275,11 @@ <h3 class="font-bold text-lg dark:text-white">
252275
(close)="closePriceComponentDrawer($event)">
253276
</app-price-component-drawer>
254277

278+
<!-- Drawer para crear Tier Pricing -->
279+
<app-tier-pricing-drawer *ngIf="showTierPricingDrawer"
280+
[prodChars]="prodSpec?.productSpecCharacteristic"
281+
(saveTierPricing)="closeTierPricingDrawer($event)"
282+
(close)="closeTierPricingDrawer(null)">
283+
</app-tier-pricing-drawer>
284+
255285
</div>

0 commit comments

Comments
 (0)