feat(cart): implement cart editing policy with granular permissions

This commit is contained in:
2026-08-19 10:16:06 -03:00
parent 8659708e1a
commit 2d0664a7c7
13 changed files with 185 additions and 32 deletions

View File

@@ -29,7 +29,11 @@
[subtotal]="cartSubtotal()"
[discount]="cartDiscount()"
[total]="cartTotal()"
[readonly]="!cartEditingEnabled()"
[readonly]="!canModifyCart()"
[allowModify]="canModifyCart()"
[allowUpdateQuantity]="canUpdateCartQuantity()"
[allowUpdateVariant]="canUpdateCartVariant()"
[allowDelete]="canDeleteCartItems()"
[backgroundColor]="'#ffffff'"
(closed)="isCartOpen.set(false)"
>

View File

@@ -32,6 +32,13 @@ const tenant: Tenant = {
footer_logo: 'https://example.com/footer.png',
header_bg_image: 'https://example.com/header-background.png',
footer_bg_image: 'https://example.com/footer-background.png',
cart_editing_policy: {
code: 'full',
allow_modify: true,
allow_delete: true,
allow_update_quantity: true,
allow_update_variant: true,
},
categories: [],
menues: [
{
@@ -647,7 +654,16 @@ describe('StoreLayoutComponent', () => {
});
it('hides quantity selectors when the tenant disables cart editing', () => {
tenantState.set({ ...tenant, cart_editing_enabled: false });
tenantState.set({
...tenant,
cart_editing_policy: {
code: 'disabled',
allow_modify: false,
allow_delete: false,
allow_update_quantity: false,
allow_update_variant: false,
},
});
cartState.set({
id: 1,
tenant_codigo: tenant.codigo,
@@ -676,4 +692,62 @@ describe('StoreLayoutComponent', () => {
expect(cartItem.componentInstance.readonly()).toBe(true);
expect(cartItem.nativeElement.querySelector('app-quantity-selector')).toBeNull();
});
it('shows variant selectors only for the full cart editing policy', () => {
const variantCart: Cart = {
id: 1,
tenant_codigo: tenant.codigo,
status: 'active',
subtotal: '100.00',
items: [
{
id: 1,
cantidad: 1,
precio_unitario: '100.00',
catalog_item_id: 1,
variant_id: 10,
nombre: 'Producto',
imagen: null,
variant: { id: 10, precio: '100.00', stock_tecnico: 5, values: { talle: 'M' } },
variants: [
{ id: 10, precio: '100.00', stock_tecnico: 5, values: { talle: 'M' } },
{ id: 11, precio: '100.00', stock_tecnico: 5, values: { talle: 'L' } },
],
},
],
};
tenantState.set({
...tenant,
cart_editing_policy: {
code: 'quantity_and_remove',
allow_modify: true,
allow_delete: true,
allow_update_quantity: true,
allow_update_variant: false,
},
});
cartState.set(variantCart);
const fixture = TestBed.createComponent(StoreLayoutComponent);
fixture.detectChanges();
(fixture.componentInstance as any).isCartOpen.set(true);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('app-variant-selector')).toBeNull();
expect(fixture.nativeElement.querySelector('app-quantity-selector')).not.toBeNull();
tenantState.set({
...tenant,
cart_editing_policy: {
code: 'full',
allow_modify: true,
allow_delete: true,
allow_update_quantity: true,
allow_update_variant: true,
},
});
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('app-variant-selector')).not.toBeNull();
});
});

View File

@@ -36,8 +36,18 @@ export class StoreLayoutComponent implements OnInit {
protected readonly isCartOpen = signal(false);
protected readonly isCreatingPurchase = signal(false);
protected readonly displayCart = computed(() => this.tenant()?.display_cart ?? true);
protected readonly cartEditingEnabled = computed(
() => this.tenant()?.cart_editing_enabled ?? true,
protected readonly cartEditingPolicy = computed(() => this.tenant()?.cart_editing_policy ?? null);
protected readonly canModifyCart = computed(
() => this.cartEditingPolicy()?.allow_modify ?? false,
);
protected readonly canDeleteCartItems = computed(
() => this.cartEditingPolicy()?.allow_delete ?? false,
);
protected readonly canUpdateCartQuantity = computed(
() => this.cartEditingPolicy()?.allow_update_quantity ?? false,
);
protected readonly canUpdateCartVariant = computed(
() => this.cartEditingPolicy()?.allow_update_variant ?? false,
);
protected readonly cartSubtotal = computed(() => {
@@ -92,6 +102,7 @@ export class StoreLayoutComponent implements OnInit {
attributes,
quantity: item.cantidad,
variantId: item.variant_id,
variants: item.variants,
};
}

View File

@@ -24,6 +24,7 @@ export interface CartItem {
nombre: string | null;
imagen: string | null;
variant: CartItemVariant | null;
variants?: CartItemVariant[];
}
export interface Cart {

View File

@@ -1,5 +1,15 @@
import { ApiResponse } from './api-response.interface';
export type CartEditingPolicyCode = 'disabled' | 'quantity_and_remove' | 'full';
export interface CartEditingPolicy {
code: CartEditingPolicyCode;
allow_modify: boolean;
allow_delete: boolean;
allow_update_quantity: boolean;
allow_update_variant: boolean;
}
export interface BankAccount {
id: number;
tenant_code: string;
@@ -127,7 +137,7 @@ export interface Tenant {
display_categories?: boolean;
display_seach_bar?: boolean;
display_cart?: boolean;
cart_editing_enabled?: boolean;
cart_editing_policy?: CartEditingPolicy;
display_cart_item_images?: boolean;
social_media?: SocialMedia[];
menues?: Menu[];