feat(checkout): map editing policy to cart controls
This commit is contained in:
@@ -59,18 +59,22 @@
|
||||
[discount]="cartDiscount()"
|
||||
[total]="cartTotal()"
|
||||
[readonly]="!canModifyCart()"
|
||||
[allowModify]="canModifyCart()"
|
||||
[allowModify]="true"
|
||||
[showModifyWhenReadonly]="true"
|
||||
[allowUpdateQuantity]="canUpdateCartQuantity()"
|
||||
[requireEditingMode]="
|
||||
createdPurchase()?.status === 'created' || createdPurchase()?.status === 'pending_payment'
|
||||
"
|
||||
[allowDelete]="false"
|
||||
[allowUpdateVariant]="canUpdateCartVariant()"
|
||||
[requireEditingMode]="true"
|
||||
[allowDelete]="canDeleteCartItems()"
|
||||
[persistQuantityChanges]="false"
|
||||
[persistVariantChanges]="false"
|
||||
[persistDeleteChanges]="false"
|
||||
[editing]="isEditingItems()"
|
||||
[editingDisabled]="isUpdatingItem() || isPreparingItemEdit()"
|
||||
backgroundColor="transparent"
|
||||
(editingChange)="onEditingItemsChange($event)"
|
||||
(itemQuantityChange)="onPurchaseItemQuantityChange($event)"
|
||||
(itemVariantChange)="onPurchaseItemVariantChange($event)"
|
||||
(itemRemove)="onPurchaseItemRemove($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,8 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
startCheckout: ReturnType<typeof vi.fn>;
|
||||
updateCustomerData: ReturnType<typeof vi.fn>;
|
||||
updateItemQuantity: ReturnType<typeof vi.fn>;
|
||||
updateItemVariant: ReturnType<typeof vi.fn>;
|
||||
removeItem: ReturnType<typeof vi.fn>;
|
||||
prepareItemEditing: ReturnType<typeof vi.fn>;
|
||||
cancelPurchase: ReturnType<typeof vi.fn>;
|
||||
generatePaymentIntent: ReturnType<typeof vi.fn>;
|
||||
@@ -36,7 +38,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
let tenantState: ReturnType<
|
||||
typeof signal<{
|
||||
codigo: string;
|
||||
cart_editing_policy?: CartEditingPolicy;
|
||||
checkout_editing_policy?: CartEditingPolicy;
|
||||
}>
|
||||
>;
|
||||
|
||||
@@ -60,6 +62,8 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
}),
|
||||
updateCustomerData: vi.fn(),
|
||||
updateItemQuantity: vi.fn(),
|
||||
updateItemVariant: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
prepareItemEditing: vi.fn(),
|
||||
cancelPurchase: vi.fn().mockResolvedValue({ status: 'cancelled' }),
|
||||
generatePaymentIntent: vi.fn().mockResolvedValue({
|
||||
@@ -95,7 +99,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
authUserState = signal(null);
|
||||
tenantState = signal({
|
||||
codigo: 'tenant-test',
|
||||
cart_editing_policy: {
|
||||
checkout_editing_policy: {
|
||||
code: 'full',
|
||||
allow_modify: true,
|
||||
allow_delete: true,
|
||||
@@ -448,7 +452,14 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
quantity: 3,
|
||||
});
|
||||
|
||||
expect(checkoutServiceStub.updateItemQuantity).toHaveBeenCalledWith('tenant-test', 25, 91, 3);
|
||||
expect(checkoutServiceStub.updateItemQuantity).toHaveBeenCalledWith(
|
||||
'tenant-test',
|
||||
25,
|
||||
91,
|
||||
3,
|
||||
null,
|
||||
'purchase',
|
||||
);
|
||||
expect(component.createdPurchase()).toBe(updatedPurchase);
|
||||
expect(component.isUpdatingItem()).toBe(false);
|
||||
});
|
||||
@@ -481,10 +492,10 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
expect(component.isEditingItems()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not allow item editing when the tenant disables cart editing', async () => {
|
||||
it('shows Modificar and returns to the home cart when checkout editing is disabled', async () => {
|
||||
tenantState.set({
|
||||
codigo: 'tenant-test',
|
||||
cart_editing_policy: {
|
||||
checkout_editing_policy: {
|
||||
code: 'disabled',
|
||||
allow_modify: false,
|
||||
allow_delete: false,
|
||||
@@ -492,7 +503,37 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
allow_update_variant: false,
|
||||
},
|
||||
});
|
||||
const { component } = createComponent();
|
||||
const { fixture, component } = createComponent();
|
||||
component.createdPurchase.set({
|
||||
id: 25,
|
||||
status: 'created',
|
||||
items: [
|
||||
{
|
||||
id: 91,
|
||||
quantity: 2,
|
||||
unit_price: '100.00',
|
||||
line_total: '200.00',
|
||||
source_catalog_item_id: 8,
|
||||
source_variant_id: null,
|
||||
item_details: {
|
||||
nombre: 'Remera',
|
||||
descripcion: null,
|
||||
slug: 'remera',
|
||||
imagen: null,
|
||||
attributes: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
subtotal: '200.00',
|
||||
total: '200.00',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const modifyButton = (fixture.nativeElement as HTMLElement).querySelector('.cart-edit-btn');
|
||||
expect(modifyButton?.textContent?.trim()).toBe('Modificar');
|
||||
expect(
|
||||
(fixture.nativeElement as HTMLElement).querySelector('app-quantity-selector'),
|
||||
).toBeNull();
|
||||
|
||||
await component.onEditingItemsChange(true);
|
||||
|
||||
@@ -500,6 +541,9 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
expect(component.canModifyCart()).toBe(false);
|
||||
expect(component.isEditingItems()).toBe(false);
|
||||
expect(checkoutServiceStub.prepareItemEditing).not.toHaveBeenCalled();
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith(['/'], {
|
||||
queryParams: { openCart: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('updates customer data on the existing purchase before payment', async () => {
|
||||
|
||||
@@ -80,10 +80,16 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
protected readonly isLoadingPurchase = signal(true);
|
||||
protected readonly checkoutStepIndex = signal(0);
|
||||
protected readonly canUpdateCartQuantity = computed(
|
||||
() => this.tenantService.tenant()?.cart_editing_policy?.allow_update_quantity ?? false,
|
||||
() => this.tenantService.tenant()?.checkout_editing_policy?.allow_update_quantity ?? false,
|
||||
);
|
||||
protected readonly canUpdateCartVariant = computed(
|
||||
() => this.tenantService.tenant()?.checkout_editing_policy?.allow_update_variant ?? false,
|
||||
);
|
||||
protected readonly canDeleteCartItems = computed(
|
||||
() => this.tenantService.tenant()?.checkout_editing_policy?.allow_delete ?? false,
|
||||
);
|
||||
protected readonly canModifyCart = computed(
|
||||
() => this.tenantService.tenant()?.cart_editing_policy?.allow_modify ?? false,
|
||||
() => this.tenantService.tenant()?.checkout_editing_policy?.allow_modify ?? false,
|
||||
);
|
||||
|
||||
protected readonly cartSubtotal = computed(() => {
|
||||
@@ -176,6 +182,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
value: attribute.value === null ? '' : String(attribute.value),
|
||||
})),
|
||||
quantity: item.quantity,
|
||||
variantId: item.source_variant_id,
|
||||
variants: item.variants,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -185,6 +193,9 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (editing && !this.canModifyCart()) {
|
||||
await this.router.navigate(['/'], {
|
||||
queryParams: { openCart: true },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,6 +262,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
purchaseId,
|
||||
itemId,
|
||||
event.quantity,
|
||||
this.createdPurchase()?.cart_id ?? null,
|
||||
this.createdPurchase()?.items_source ?? 'purchase',
|
||||
);
|
||||
this.createdPurchase.set(purchase);
|
||||
} catch (error) {
|
||||
@@ -260,6 +273,79 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
protected async onPurchaseItemVariantChange(event: {
|
||||
item: CartItemMock;
|
||||
variantId: number;
|
||||
}): Promise<void> {
|
||||
const tenant = this.tenantService.tenant();
|
||||
const purchase = this.createdPurchase();
|
||||
const itemId = event.item.cartItemId;
|
||||
|
||||
if (
|
||||
!this.canUpdateCartVariant() ||
|
||||
!tenant ||
|
||||
!purchase ||
|
||||
!itemId ||
|
||||
this.isUpdatingItem() ||
|
||||
!this.isEditingItems()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUpdatingItem.set(true);
|
||||
try {
|
||||
this.createdPurchase.set(
|
||||
await this.checkoutService.updateItemVariant(
|
||||
tenant.codigo,
|
||||
purchase.id,
|
||||
itemId,
|
||||
event.variantId,
|
||||
event.item.quantity,
|
||||
purchase.cart_id,
|
||||
purchase.items_source,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to update purchase item variant:', error);
|
||||
} finally {
|
||||
this.isUpdatingItem.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async onPurchaseItemRemove(event: { item: CartItemMock }): Promise<void> {
|
||||
const tenant = this.tenantService.tenant();
|
||||
const purchase = this.createdPurchase();
|
||||
const itemId = event.item.cartItemId;
|
||||
|
||||
if (
|
||||
!this.canDeleteCartItems() ||
|
||||
!tenant ||
|
||||
!purchase ||
|
||||
!itemId ||
|
||||
this.isUpdatingItem() ||
|
||||
!this.isEditingItems()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUpdatingItem.set(true);
|
||||
try {
|
||||
this.createdPurchase.set(
|
||||
await this.checkoutService.removeItem(
|
||||
tenant.codigo,
|
||||
purchase.id,
|
||||
itemId,
|
||||
purchase.cart_id,
|
||||
purchase.items_source,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to remove purchase item:', error);
|
||||
} finally {
|
||||
this.isUpdatingItem.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async onStep1Continue(): Promise<void> {
|
||||
if (this.form.invalid || this.isUpdatingPurchase() || this.isEditingItems()) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user