diff --git a/src/app/providers/TenantProvider.tsx b/src/app/providers/TenantProvider.tsx index c8088b7..25f7044 100644 --- a/src/app/providers/TenantProvider.tsx +++ b/src/app/providers/TenantProvider.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useEffect, useMemo, useCallback } from 'react'; import { DEFAULT_TENANT_ID, createRequestContext, @@ -22,22 +22,18 @@ export const TenantProvider: React.FC = ({ initialTenantId, }) => { const sessionTenantId = useSessionStore((s) => s.activeTenantId); - const [currentTenantId, setCurrentTenantId] = useState(() => { - return initialTenantId || sessionTenantId || getActiveGlobalTenantId() || DEFAULT_TENANT_ID; - }); + const currentTenantId = sessionTenantId || initialTenantId || getActiveGlobalTenantId() || DEFAULT_TENANT_ID; const switchTenant = useCallback((tenantId: string) => { - setCurrentTenantId(tenantId); setActiveGlobalTenantId(tenantId); useSessionStore.getState().setActiveTenantId(tenantId); }, []); useEffect(() => { - if (sessionTenantId && sessionTenantId !== currentTenantId) { - setCurrentTenantId(sessionTenantId); - setActiveGlobalTenantId(sessionTenantId); + if (initialTenantId && initialTenantId !== sessionTenantId) { + useSessionStore.getState().setActiveTenantId(initialTenantId); } - }, [sessionTenantId, currentTenantId]); + }, [initialTenantId, sessionTenantId]); useEffect(() => { setActiveGlobalTenantId(currentTenantId); diff --git a/src/mock/store.ts b/src/mock/store.ts index 76c1b14..61cbd77 100644 --- a/src/mock/store.ts +++ b/src/mock/store.ts @@ -1,8 +1,6 @@ import { create } from 'zustand'; -import { useDemoStore as useDemoControlsStore } from '../stores/demo'; import { CapabilityId } from '../core/capabilities/types'; import { SystemRoleId } from '../core/permissions/types'; -import { BUSINESS_PROFILES } from '../core/profiles/profiles'; import { BusinessProfile } from '../core/profiles/types'; export type ThemeMode = 'soft-light' | 'light' | 'dark'; diff --git a/src/services/__tests__/orderStateMachine.test.ts b/src/services/__tests__/orderStateMachine.test.ts new file mode 100644 index 0000000..c401bd9 --- /dev/null +++ b/src/services/__tests__/orderStateMachine.test.ts @@ -0,0 +1,280 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { resetDatabase } from '../storage/indexedDb'; +import { getServices } from '../mock'; +import { Order } from '../../entities/commerce/order'; + +describe('Order State Machine & Shipment/Return Lifecycle Suite (F04, F05)', () => { + const { commerce, inventory, finance } = getServices(); + const WAREHOUSE_ID = 'wh_sm_test'; + + beforeEach(async () => { + await resetDatabase(); + // Ensure default bank account exists in finance + await finance.accounts.create({ + name: 'Расчётный счёт компании', + type: 'bank', + balance: { minor: 10000000, currency: 'RUB' }, + currency: 'RUB', + isDefault: true, + status: 'active', + }); + }); + + const createDraftOrder = async (productId: string, qty = 5, channel: Order['channel'] = 'web') => { + // Seed initial stock + await inventory.adjustStock(WAREHOUSE_ID, productId, 20, 'Начальный остаток для SM'); + + return commerce.orders.create({ + orderNumber: `ORD-SM-${Date.now().toString().slice(-4)}`, + customerType: 'contact', + customerId: 'cust_sm', + customerName: 'Клиент SM', + status: 'draft', + paymentStatus: 'unpaid', + fulfillmentStatus: 'unfulfilled', + warehouseId: WAREHOUSE_ID, + items: [ + { + id: 'item_sm_1', + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: qty, + unitPrice: { minor: 100000, currency: 'RUB' }, + discountMinor: 0, + totalPrice: { minor: qty * 100000, currency: 'RUB' }, + }, + ], + subtotal: { minor: qty * 100000, currency: 'RUB' }, + discountMinor: 0, + taxMinor: 0, + shippingMinor: 0, + total: { minor: qty * 100000, currency: 'RUB' }, + channel, + } as Omit); + }; + + it('1. Executes valid state machine progression: draft -> confirmed -> preparing -> ready_for_shipment -> shipped -> delivered -> completed', async () => { + const order = await createDraftOrder('prod_sm_1', 3, 'web'); + expect(order.status).toBe('draft'); + + // 1. draft -> confirmed (via confirmOrder) + const confirmed = await commerce.confirmOrder(order.id, WAREHOUSE_ID); + expect(confirmed.status).toBe('confirmed'); + + // 2. confirmed -> preparing + const preparing = await commerce.updateOrderStatus(order.id, 'preparing'); + expect(preparing.status).toBe('preparing'); + + // 3. preparing -> ready_for_shipment + const ready = await commerce.updateOrderStatus(order.id, 'ready_for_shipment'); + expect(ready.status).toBe('ready_for_shipment'); + + // Pay order before shipping (for B2C/web channel) + await commerce.createPayment(order.id, ready.total, 'card', true); + + // 4. ready_for_shipment -> shipped + const shipped = await commerce.updateOrderStatus(order.id, 'shipped'); + expect(shipped.status).toBe('shipped'); + + // 5. shipped -> delivered + const delivered = await commerce.updateOrderStatus(order.id, 'delivered'); + expect(delivered.status).toBe('delivered'); + + // 6. delivered -> completed + const completed = await commerce.updateOrderStatus(order.id, 'completed'); + expect(completed.status).toBe('completed'); + }); + + it('2. Rejects invalid state transitions (draft -> shipped, completed -> cancelled, shipped -> cancelled)', async () => { + const order = await createDraftOrder('prod_sm_2', 2, 'web'); + + // Direct illegal transition from draft to shipped + await expect(commerce.updateOrderStatus(order.id, 'shipped')).rejects.toThrow( + /Недопустимый переход статуса/ + ); + + // Direct illegal transition from draft to ready_for_shipment + await expect(commerce.updateOrderStatus(order.id, 'ready_for_shipment')).rejects.toThrow( + /Недопустимый переход статуса/ + ); + + // Confirm order + await commerce.confirmOrder(order.id, WAREHOUSE_ID); + await commerce.createPayment(order.id, order.total, 'card', false); + await commerce.createShipment(order.id, WAREHOUSE_ID); + + // After shipping, cannot cancel! + await expect(commerce.updateOrderStatus(order.id, 'cancelled')).rejects.toThrow( + /Недопустимый переход статуса/ + ); + await expect(commerce.cancelOrder(order.id)).rejects.toThrow( + /Отменить можно только черновик или подтверждённый заказ/ + ); + }); + + it('3. Shipment precondition: unpaid retail orders cannot be shipped; B2B orders allow postpaid shipment', async () => { + // A) Retail order unpaid -> createShipment must be blocked + const retailOrder = await createDraftOrder('prod_sm_retail', 2, 'web'); + await commerce.confirmOrder(retailOrder.id, WAREHOUSE_ID); + await commerce.updateOrderStatus(retailOrder.id, 'preparing'); + await commerce.updateOrderStatus(retailOrder.id, 'ready_for_shipment'); + + await expect(commerce.createShipment(retailOrder.id, WAREHOUSE_ID)).rejects.toThrow( + /Отгрузка невозможна: заказ не оплачен/ + ); + + // Pay retail order -> now createShipment succeeds! + await commerce.createPayment(retailOrder.id, retailOrder.total, 'card', false); + const retailShipment = await commerce.createShipment(retailOrder.id, WAREHOUSE_ID); + expect(retailShipment.status).toBe('ready'); + const updatedRetailOrder = await commerce.orders.getById(retailOrder.id); + expect(updatedRetailOrder?.status).toBe('shipped'); + + // B) B2B order unpaid -> postpaid shipment allowed + const b2bOrder = await createDraftOrder('prod_sm_b2b', 4, 'b2b'); + await commerce.confirmOrder(b2bOrder.id, WAREHOUSE_ID); + await commerce.updateOrderStatus(b2bOrder.id, 'preparing'); + await commerce.updateOrderStatus(b2bOrder.id, 'ready_for_shipment'); + + const b2bShipment = await commerce.createShipment(b2bOrder.id, WAREHOUSE_ID); + expect(b2bShipment.status).toBe('ready'); + const updatedB2BOrder = await commerce.orders.getById(b2bOrder.id); + expect(updatedB2BOrder?.status).toBe('shipped'); + }); + + it('4. createShipment fulfills active reservation and deducts physical stock onHand', async () => { + const productId = 'prod_sm_stock'; + const order = await createDraftOrder(productId, 5, 'b2b'); + + // Before confirmation: onHand = 20, reserved = 0, available = 20 + let stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(20); + expect(stock.reserved).toBe(0); + + // Confirm: onHand = 20, reserved = 5, available = 15 + await commerce.confirmOrder(order.id, WAREHOUSE_ID); + stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(20); + expect(stock.reserved).toBe(5); + expect(stock.available).toBe(15); + + // Create Shipment: physical onHand reduces to 15, reserved cleared to 0, available = 15 + await commerce.createShipment(order.id, WAREHOUSE_ID); + stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(15); + expect(stock.reserved).toBe(0); + expect(stock.available).toBe(15); + + // Reservation is marked 'fulfilled' + const resList = await inventory.reservations.list({ filter: { orderId: order.id } }); + expect(resList.items[0].status).toBe('fulfilled'); + }); + + it('5. createReturn: allows return only on shipped/delivered orders, checks max returnable qty, restores onHand, and records outflow in finance', async () => { + const productId = 'prod_sm_return'; + const order = await createDraftOrder(productId, 6, 'b2b'); + + // 1. Attempting return on draft order must be rejected + await expect( + commerce.createReturn(order.id, 'Брак', [ + { + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: 2, + refundAmount: { minor: 200000, currency: 'RUB' }, + }, + ]) + ).rejects.toThrow(/Возврат разрешён только по отгруженным или доставленным/); + + // Confirm and Ship order (qty = 6) + await commerce.confirmOrder(order.id, WAREHOUSE_ID); + await commerce.createShipment(order.id, WAREHOUSE_ID); + + // Stock onHand after shipment: 20 - 6 = 14 + let stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(14); + + // 2. Attempting return of 7 when only 6 were shipped must be rejected + await expect( + commerce.createReturn(order.id, 'Отказ', [ + { + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: 7, + refundAmount: { minor: 700000, currency: 'RUB' }, + }, + ]) + ).rejects.toThrow(/превышает отгруженное доступное/); + + // 3. Valid return of 2 items + const ret1 = await commerce.createReturn( + order.id, + 'Частичный отказ клиента', + [ + { + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: 2, + refundAmount: { minor: 200000, currency: 'RUB' }, + }, + ], + WAREHOUSE_ID, + true // auto-record finance transaction + ); + + expect(ret1.status).toBe('refunded'); + expect(ret1.totalRefund.minor).toBe(200000); + + // Verify physical stock onHand restored from 14 -> 16 + stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(16); + expect(stock.available).toBe(16); + + // Verify finance outflow transaction recorded + const txList = await finance.transactions.list({ filter: { referenceId: ret1.id } }); + expect(txList.items).toHaveLength(1); + expect(txList.items[0].type).toBe('outflow'); + expect(txList.items[0].category).toBe('refund'); + expect(txList.items[0].amount.minor).toBe(200000); + + // 4. Second return: can return up to 4 more. Trying to return 5 must be rejected! + await expect( + commerce.createReturn(order.id, 'Лишний возврат', [ + { + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: 5, + refundAmount: { minor: 500000, currency: 'RUB' }, + }, + ]) + ).rejects.toThrow(/превышает отгруженное доступное/); + + // Returning exactly the remaining 4 items succeeds + const ret2 = await commerce.createReturn( + order.id, + 'Возврат остатка партии', + [ + { + productId, + sku: 'SKU-SM', + name: 'Товар SM', + quantity: 4, + refundAmount: { minor: 400000, currency: 'RUB' }, + }, + ], + WAREHOUSE_ID, + true + ); + expect(ret2.status).toBe('refunded'); + + // Stock onHand restored from 16 -> 20 (original initial balance) + stock = (await inventory.stockItems.list({ filter: { warehouseId: WAREHOUSE_ID, productId } })).items[0]; + expect(stock.onHand).toBe(20); + expect(stock.available).toBe(20); + }); +}); diff --git a/src/services/contracts/commerce.ts b/src/services/contracts/commerce.ts index 9232093..3b46ee0 100644 --- a/src/services/contracts/commerce.ts +++ b/src/services/contracts/commerce.ts @@ -79,5 +79,18 @@ export interface ICommerceService { idempotencyKey?: string ): Promise; createShipment(orderId: string, warehouseId: string, items?: ShipmentItem[]): Promise; - processReturn(orderId: string, reason: string, items: ReturnItem[], warehouseId?: string, autoRecordFinance?: boolean): Promise; + createReturn( + orderId: string, + reason: string, + items: ReturnItem[], + warehouseId?: string, + autoRecordFinance?: boolean + ): Promise; + processReturn( + orderId: string, + reason: string, + items: ReturnItem[], + warehouseId?: string, + autoRecordFinance?: boolean + ): Promise; } diff --git a/src/services/mock/commerceService.ts b/src/services/mock/commerceService.ts index 903059e..a649623 100644 --- a/src/services/mock/commerceService.ts +++ b/src/services/mock/commerceService.ts @@ -8,15 +8,27 @@ import { ReturnItem, } from '../contracts/commerce'; import { IRepository } from '../contracts/repository'; -import { Order } from '../../entities/commerce/order'; +import { Order, OrderStatus } from '../../entities/commerce/order'; import { Product, ProductCategory } from '../../entities/commerce/product'; import { MoneyAmount } from '../../entities/types'; import { BaseMockRepository } from './baseRepository'; import { ulid } from '../../shared/lib/ulid'; import { getServices } from './index'; - import { InsufficientStockError } from '../contracts/inventory'; +const ALLOWED_ORDER_TRANSITIONS: Record = { + draft: ['confirmed', 'cancelled'], + confirmed: ['preparing', 'processing', 'cancelled'], + preparing: ['ready_for_shipment', 'ready'], + processing: ['ready_for_shipment', 'ready'], + ready_for_shipment: ['shipped'], + ready: ['shipped'], + shipped: ['delivered'], + delivered: ['completed'], + completed: [], + cancelled: [], +}; + export class MockCommerceService implements ICommerceService { products: IRepository; categories: IRepository; @@ -36,8 +48,41 @@ export class MockCommerceService implements ICommerceService { this.returns = new BaseMockRepository('return'); } - async updateOrderStatus(orderId: string, status: Order['status']): Promise { - return this.orders.update(orderId, { status }); + async updateOrderStatus(orderId: string, nextStatus: OrderStatus): Promise { + const order = await this.orders.getById(orderId); + if (!order) { + throw new Error(`Заказ с ID ${orderId} не найден.`); + } + + if (order.status === nextStatus) { + return order; + } + + const allowed = ALLOWED_ORDER_TRANSITIONS[order.status] || []; + if (!allowed.includes(nextStatus)) { + throw new Error( + `Недопустимый переход статуса заказа: «${order.status}» -> «${nextStatus}». Запрещено нарушать State Machine заказа.` + ); + } + + // Direct routing to full operations for confirmed and cancelled + if (nextStatus === 'confirmed') { + return this.confirmOrder(orderId); + } + if (nextStatus === 'cancelled') { + return this.cancelOrder(orderId); + } + + // Pre-condition for shipped status: must be paid or B2B credit + if (nextStatus === 'shipped') { + const isPaid = order.paymentStatus === 'paid' || order.paymentStatus === 'overpaid'; + const isB2B = order.channel === 'b2b'; + if (!isPaid && !isB2B) { + throw new Error('Отгрузка невозможна: заказ не оплачен (для розницы требуется 100% предоплата)'); + } + } + + return this.orders.update(orderId, { status: nextStatus }); } async confirmOrder(orderId: string, warehouseId = 'wh_main'): Promise { @@ -215,6 +260,22 @@ export class MockCommerceService implements ICommerceService { throw new Error(`Заказ с ID ${orderId} не найден.`); } + if (order.status === 'draft') { + throw new Error('Нельзя отгрузить черновик заказа (draft). Сначала подтвердите заказ.'); + } + if (order.status === 'cancelled') { + throw new Error('Нельзя отгрузить отмененный заказ (cancelled).'); + } + if (order.status === 'completed') { + throw new Error('Заказ уже полностью выполнен (completed).'); + } + + const isPaid = order.paymentStatus === 'paid' || order.paymentStatus === 'overpaid'; + const isB2B = order.channel === 'b2b'; + if (!isPaid && !isB2B) { + throw new Error('Отгрузка невозможна: заказ не оплачен (для розницы требуется 100% предоплата)'); + } + const shipmentItems: ShipmentItem[] = items || order.items.map((i) => ({ @@ -237,7 +298,7 @@ export class MockCommerceService implements ICommerceService { await this.orders.update(orderId, { fulfillmentStatus: 'fulfilled', status: 'shipped' }); - // Deduct reserved stock and physical stock in inventory + // Deduct physical stock (onHand) and fulfill active reservations in inventory const inventory = getServices().inventory; if (inventory) { await inventory.fulfillOrderReservations(orderId, warehouseId, shipmentItems); @@ -246,18 +307,56 @@ export class MockCommerceService implements ICommerceService { return shipment; } - async processReturn( + async createReturn( orderId: string, reason: string, items: ReturnItem[], warehouseId = 'wh_main', - autoRecordFinance = false + autoRecordFinance = true ): Promise { const order = await this.orders.getById(orderId); if (!order) { throw new Error(`Заказ с ID ${orderId} не найден.`); } + // Return is strictly allowed only for shipped, delivered, or completed orders + const isEligibleForReturn = + order.status === 'shipped' || + order.status === 'delivered' || + order.status === 'completed' || + order.fulfillmentStatus === 'fulfilled'; + + if (!isEligibleForReturn) { + throw new Error( + `Возврат разрешён только по отгруженным или доставленным заказам (shipped, delivered, completed). Текущий статус: «${order.status}»` + ); + } + + // Validate quantities against fulfilled quantities minus already returned quantities + const previousReturns = await this.returns.list({ filter: { orderId } }); + + for (const item of items) { + const orderItem = order.items.find((i) => i.productId === item.productId); + if (!orderItem) { + throw new Error(`Товар с ID «${item.productId}» не найден в позициях заказа`); + } + + let alreadyReturnedQty = 0; + for (const pr of previousReturns.items) { + const prevItem = pr.items.find((pi) => pi.productId === item.productId); + if (prevItem) { + alreadyReturnedQty += prevItem.quantity; + } + } + + const maxAllowedReturn = orderItem.quantity - alreadyReturnedQty; + if (item.quantity > maxAllowedReturn) { + throw new Error( + `Количество возврата (${item.quantity}) превышает отгруженное доступное для возврата количество (${maxAllowedReturn}) для товара «${item.name || item.productId}»` + ); + } + } + const totalMinor = items.reduce((sum, item) => sum + item.refundAmount.minor, 0); const ret = await this.returns.create({ @@ -274,7 +373,7 @@ export class MockCommerceService implements ICommerceService { paymentStatus: 'refunded', }); - // Restore physical stock in inventory + // Restore physical stock onHand in inventory const inventory = getServices().inventory; if (inventory) { for (const item of items) { @@ -287,7 +386,7 @@ export class MockCommerceService implements ICommerceService { } } - // Automatically record refund transaction in finance if requested + // Automatically record refund transaction in finance if (autoRecordFinance) { const finance = getServices().finance; if (finance) { @@ -297,4 +396,14 @@ export class MockCommerceService implements ICommerceService { return ret; } + + async processReturn( + orderId: string, + reason: string, + items: ReturnItem[], + warehouseId = 'wh_main', + autoRecordFinance = true + ): Promise { + return this.createReturn(orderId, reason, items, warehouseId, autoRecordFinance); + } } diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index f3ba2ce..03b510f 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/app/guards/routeguard.tsx","./src/app/layouts/appshell.tsx","./src/app/providers/appproviders.tsx","./src/app/providers/tenantprovider.tsx","./src/app/providers/tenantcontext.ts","./src/app/router/index.tsx","./src/app/shell/bottomnav.tsx","./src/app/shell/commandpalette.tsx","./src/app/shell/mobilemenudrawer.tsx","./src/app/shell/notificationsdrawer.tsx","./src/app/shell/quickactionsmodal.tsx","./src/app/shell/rightcommandpanel.tsx","./src/app/shell/sidebar.tsx","./src/app/shell/topbar.tsx","./src/core/capabilities/types.ts","./src/core/modules/registry.ts","./src/core/modules/types.ts","./src/core/permissions/types.ts","./src/core/profiles/profiles.ts","./src/core/profiles/types.ts","./src/demo/democlock.ts","./src/demo/index.ts","./src/demo/scenarios.ts","./src/demo/seedstate.ts","./src/entities/index.ts","./src/entities/types.ts","./src/entities/commerce/order.ts","./src/entities/commerce/product.ts","./src/entities/crm/company.ts","./src/entities/crm/contact.ts","./src/entities/crm/deal.ts","./src/entities/crm/lead.ts","./src/entities/inventory/warehouse.ts","./src/entities/metadata/registry.ts","./src/features/__tests__/accessibilityandnavigation.test.tsx","./src/features/__tests__/configurationandcrminteractions.test.tsx","./src/features/__tests__/fieldtypescontract.test.ts","./src/features/__tests__/fieldtypesintegration.test.tsx","./src/features/__tests__/localizationcleanliness.test.ts","./src/features/__tests__/phasefhardening.test.tsx","./src/features/__tests__/pilot1flows.test.ts","./src/features/__tests__/targetprototypeflows.test.tsx","./src/features/ai/aicentermodule.tsx","./src/features/analytics/analyticsmodule.tsx","./src/features/apps/appscatalogmodule.tsx","./src/features/automation/automationmodule.tsx","./src/features/catalog/catalogfilterdrawer.tsx","./src/features/catalog/modulecard.tsx","./src/features/catalog/moduleinfomodal.tsx","./src/features/catalog/modulescatalog.tsx","./src/features/catalog/catalogdata.ts","./src/features/cms/cmsmodule.tsx","./src/features/communications/communicationsmodule.tsx","./src/features/crm/crmmodule.tsx","./src/features/crm/csvimportwizard.tsx","./src/features/crm/constants.ts","./src/features/crm/components/crmentitymodals.tsx","./src/features/crm/components/crmkanbanview.tsx","./src/features/crm/components/crmtableview.tsx","./src/features/crm/components/customer360drawer.tsx","./src/features/crm/components/dealwonordermodal.tsx","./src/features/crm/components/leadconversionmodal.tsx","./src/features/crm/components/index.ts","./src/features/dashboard/dynamicdashboard.tsx","./src/features/employees/employeesmodule.tsx","./src/features/finance/financemodule.tsx","./src/features/helpdesk/helpdeskmodule.tsx","./src/features/inventory/inventorymodule.tsx","./src/features/marketing/marketingmodule.tsx","./src/features/metadata/customentitypage.tsx","./src/features/metadata/dynamiccardrenderer.tsx","./src/features/metadata/dynamicformrenderer.tsx","./src/features/metadata/dynamictable.tsx","./src/features/metadata/__tests__/dynamicrenderer.test.tsx","./src/features/operations/baysview.tsx","./src/features/operations/bookingcalendarview.tsx","./src/features/operations/deliveryview.tsx","./src/features/operations/diningview.tsx","./src/features/operations/kdsview.tsx","./src/features/operations/operationsmodule.tsx","./src/features/operations/posview.tsx","./src/features/operations/purchasingview.tsx","./src/features/operations/recipesview.tsx","./src/features/operations/workordersview.tsx","./src/features/sales/salesmodule.tsx","./src/features/settings/settingsmodule.tsx","./src/features/settings/entities/cardlayoutbuilder.tsx","./src/features/settings/entities/entitycatalog.tsx","./src/features/settings/entities/fieldbuilder.tsx","./src/features/settings/entities/formbuilder.tsx","./src/features/settings/entities/viewbuilder.tsx","./src/features/settings/entities/fieldtypesmeta.ts","./src/features/settings/entities/useformbuilderhistory.ts","./src/features/settings/entities/useviewbuildercolumns.ts","./src/features/settings/entities/__tests__/configurationengine.test.tsx","./src/features/tasks/tasksmodule.tsx","./src/mock/mockapi.ts","./src/mock/mockdata.ts","./src/mock/store.ts","./src/pages/aipage.tsx","./src/pages/analyticspage.tsx","./src/pages/appspage.tsx","./src/pages/automationpage.tsx","./src/pages/cardlayoutbuilderpage.tsx","./src/pages/cmspage.tsx","./src/pages/communicationspage.tsx","./src/pages/crmpage.tsx","./src/pages/customentityroutepage.tsx","./src/pages/dashboardpage.tsx","./src/pages/employeespage.tsx","./src/pages/entitycatalogpage.tsx","./src/pages/fieldbuilderpage.tsx","./src/pages/financepage.tsx","./src/pages/formbuilderpage.tsx","./src/pages/helpdeskpage.tsx","./src/pages/inventorypage.tsx","./src/pages/marketingpage.tsx","./src/pages/operationspage.tsx","./src/pages/procurementpage.tsx","./src/pages/salespage.tsx","./src/pages/settingspage.tsx","./src/pages/taskspage.tsx","./src/pages/viewbuilderpage.tsx","./src/pages/index.ts","./src/pages/error/forbiddenpage.tsx","./src/pages/error/notfoundpage.tsx","./src/services/__tests__/tenantisolation.test.ts","./src/services/contracts/audit.ts","./src/services/contracts/commerce.ts","./src/services/contracts/context.ts","./src/services/contracts/crm.ts","./src/services/contracts/finance.ts","./src/services/contracts/index.ts","./src/services/contracts/inventory.ts","./src/services/contracts/metadata.ts","./src/services/contracts/repository.ts","./src/services/hooks/index.ts","./src/services/hooks/useauditlog.ts","./src/services/hooks/usecrm.ts","./src/services/hooks/usefinance.ts","./src/services/hooks/useinventory.ts","./src/services/hooks/usemetadata.ts","./src/services/hooks/useorders.ts","./src/services/hooks/usestoragelistener.ts","./src/services/mock/auditservice.ts","./src/services/mock/baserepository.ts","./src/services/mock/commerceservice.ts","./src/services/mock/crmservice.ts","./src/services/mock/financeservice.ts","./src/services/mock/index.ts","./src/services/mock/inventoryservice.ts","./src/services/mock/metadataservice.ts","./src/services/mock/__tests__/services.test.ts","./src/services/storage/eventbus.ts","./src/services/storage/index.ts","./src/services/storage/indexeddb.ts","./src/services/storage/__tests__/indexeddb.test.ts","./src/shared/lib/currency.ts","./src/shared/lib/date.ts","./src/shared/lib/dnd.ts","./src/shared/lib/fieldengine.ts","./src/shared/lib/index.ts","./src/shared/lib/localizationdictionary.ts","./src/shared/lib/ulid.ts","./src/shared/lib/useresponsive.ts","./src/shared/lib/__tests__/ulid.test.ts","./src/shared/ui/badge.tsx","./src/shared/ui/button.tsx","./src/shared/ui/checkbox.tsx","./src/shared/ui/drawer.tsx","./src/shared/ui/emptystate.tsx","./src/shared/ui/errorboundary.tsx","./src/shared/ui/iconbutton.tsx","./src/shared/ui/input.tsx","./src/shared/ui/maturitybadge.tsx","./src/shared/ui/modal.tsx","./src/shared/ui/select.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/statcard.tsx","./src/shared/ui/toast.tsx","./src/shared/ui/toastcontext.ts","./src/shared/ui/index.ts","./src/shared/ui/usetoast.ts","./src/shared/utils/formatters.ts","./src/stores/demo.ts","./src/stores/index.ts","./src/stores/navigation.ts","./src/stores/theme.ts","./src/test/setup.ts"],"version":"6.0.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/app/guards/routeguard.tsx","./src/app/layouts/appshell.tsx","./src/app/providers/appproviders.tsx","./src/app/providers/tenantprovider.tsx","./src/app/providers/tenantcontext.ts","./src/app/router/index.tsx","./src/app/shell/bottomnav.tsx","./src/app/shell/commandpalette.tsx","./src/app/shell/mobilemenudrawer.tsx","./src/app/shell/notificationsdrawer.tsx","./src/app/shell/quickactionsmodal.tsx","./src/app/shell/rightcommandpanel.tsx","./src/app/shell/sidebar.tsx","./src/app/shell/topbar.tsx","./src/core/capabilities/types.ts","./src/core/modules/registry.ts","./src/core/modules/types.ts","./src/core/permissions/types.ts","./src/core/profiles/profiles.ts","./src/core/profiles/types.ts","./src/core/session/sessionstore.ts","./src/demo/democlock.ts","./src/demo/index.ts","./src/demo/scenarios.ts","./src/demo/seedstate.ts","./src/entities/index.ts","./src/entities/types.ts","./src/entities/commerce/order.ts","./src/entities/commerce/product.ts","./src/entities/crm/company.ts","./src/entities/crm/contact.ts","./src/entities/crm/deal.ts","./src/entities/crm/lead.ts","./src/entities/inventory/warehouse.ts","./src/entities/metadata/registry.ts","./src/features/__tests__/accessibilityandnavigation.test.tsx","./src/features/__tests__/configurationandcrminteractions.test.tsx","./src/features/__tests__/fieldtypescontract.test.ts","./src/features/__tests__/fieldtypesintegration.test.tsx","./src/features/__tests__/localizationcleanliness.test.ts","./src/features/__tests__/phasefhardening.test.tsx","./src/features/__tests__/pilot1flows.test.ts","./src/features/__tests__/rbacsessionintegrity.test.tsx","./src/features/__tests__/targetprototypeflows.test.tsx","./src/features/ai/aicentermodule.tsx","./src/features/analytics/analyticsmodule.tsx","./src/features/apps/appscatalogmodule.tsx","./src/features/automation/automationmodule.tsx","./src/features/catalog/catalogfilterdrawer.tsx","./src/features/catalog/modulecard.tsx","./src/features/catalog/moduleinfomodal.tsx","./src/features/catalog/modulescatalog.tsx","./src/features/catalog/catalogdata.ts","./src/features/cms/cmsmodule.tsx","./src/features/communications/communicationsmodule.tsx","./src/features/crm/crmmodule.tsx","./src/features/crm/csvimportwizard.tsx","./src/features/crm/constants.ts","./src/features/crm/components/crmentitymodals.tsx","./src/features/crm/components/crmkanbanview.tsx","./src/features/crm/components/crmtableview.tsx","./src/features/crm/components/customer360drawer.tsx","./src/features/crm/components/dealwonordermodal.tsx","./src/features/crm/components/leadconversionmodal.tsx","./src/features/crm/components/index.ts","./src/features/dashboard/dynamicdashboard.tsx","./src/features/employees/employeesmodule.tsx","./src/features/finance/financemodule.tsx","./src/features/helpdesk/helpdeskmodule.tsx","./src/features/inventory/inventorymodule.tsx","./src/features/marketing/marketingmodule.tsx","./src/features/metadata/customentitypage.tsx","./src/features/metadata/dynamiccardrenderer.tsx","./src/features/metadata/dynamicformrenderer.tsx","./src/features/metadata/dynamictable.tsx","./src/features/metadata/__tests__/dynamicrenderer.test.tsx","./src/features/operations/baysview.tsx","./src/features/operations/bookingcalendarview.tsx","./src/features/operations/deliveryview.tsx","./src/features/operations/diningview.tsx","./src/features/operations/kdsview.tsx","./src/features/operations/operationsmodule.tsx","./src/features/operations/posview.tsx","./src/features/operations/purchasingview.tsx","./src/features/operations/recipesview.tsx","./src/features/operations/workordersview.tsx","./src/features/sales/salesmodule.tsx","./src/features/settings/settingsmodule.tsx","./src/features/settings/entities/cardlayoutbuilder.tsx","./src/features/settings/entities/entitycatalog.tsx","./src/features/settings/entities/fieldbuilder.tsx","./src/features/settings/entities/formbuilder.tsx","./src/features/settings/entities/viewbuilder.tsx","./src/features/settings/entities/fieldtypesmeta.ts","./src/features/settings/entities/useformbuilderhistory.ts","./src/features/settings/entities/useviewbuildercolumns.ts","./src/features/settings/entities/__tests__/configurationengine.test.tsx","./src/features/tasks/tasksmodule.tsx","./src/mock/mockapi.ts","./src/mock/mockdata.ts","./src/mock/store.ts","./src/pages/aipage.tsx","./src/pages/analyticspage.tsx","./src/pages/appspage.tsx","./src/pages/automationpage.tsx","./src/pages/cardlayoutbuilderpage.tsx","./src/pages/cmspage.tsx","./src/pages/communicationspage.tsx","./src/pages/crmpage.tsx","./src/pages/customentityroutepage.tsx","./src/pages/dashboardpage.tsx","./src/pages/employeespage.tsx","./src/pages/entitycatalogpage.tsx","./src/pages/fieldbuilderpage.tsx","./src/pages/financepage.tsx","./src/pages/formbuilderpage.tsx","./src/pages/helpdeskpage.tsx","./src/pages/inventorypage.tsx","./src/pages/marketingpage.tsx","./src/pages/operationspage.tsx","./src/pages/procurementpage.tsx","./src/pages/salespage.tsx","./src/pages/settingspage.tsx","./src/pages/taskspage.tsx","./src/pages/viewbuilderpage.tsx","./src/pages/index.ts","./src/pages/error/forbiddenpage.tsx","./src/pages/error/notfoundpage.tsx","./src/services/__tests__/orderreservationintegrity.test.ts","./src/services/__tests__/orderstatemachine.test.ts","./src/services/__tests__/paymentmodelintegrity.test.ts","./src/services/__tests__/tenantisolation.test.ts","./src/services/contracts/audit.ts","./src/services/contracts/commerce.ts","./src/services/contracts/context.ts","./src/services/contracts/crm.ts","./src/services/contracts/finance.ts","./src/services/contracts/index.ts","./src/services/contracts/inventory.ts","./src/services/contracts/metadata.ts","./src/services/contracts/repository.ts","./src/services/hooks/index.ts","./src/services/hooks/useauditlog.ts","./src/services/hooks/usecrm.ts","./src/services/hooks/usefinance.ts","./src/services/hooks/useinventory.ts","./src/services/hooks/usemetadata.ts","./src/services/hooks/useorders.ts","./src/services/hooks/usestoragelistener.ts","./src/services/mock/auditservice.ts","./src/services/mock/baserepository.ts","./src/services/mock/commerceservice.ts","./src/services/mock/crmservice.ts","./src/services/mock/financeservice.ts","./src/services/mock/index.ts","./src/services/mock/inventoryservice.ts","./src/services/mock/metadataservice.ts","./src/services/mock/__tests__/services.test.ts","./src/services/storage/eventbus.ts","./src/services/storage/index.ts","./src/services/storage/indexeddb.ts","./src/services/storage/__tests__/indexeddb.test.ts","./src/shared/lib/currency.ts","./src/shared/lib/date.ts","./src/shared/lib/dnd.ts","./src/shared/lib/fieldengine.ts","./src/shared/lib/index.ts","./src/shared/lib/localizationdictionary.ts","./src/shared/lib/ulid.ts","./src/shared/lib/useresponsive.ts","./src/shared/lib/__tests__/ulid.test.ts","./src/shared/ui/badge.tsx","./src/shared/ui/button.tsx","./src/shared/ui/checkbox.tsx","./src/shared/ui/drawer.tsx","./src/shared/ui/emptystate.tsx","./src/shared/ui/errorboundary.tsx","./src/shared/ui/iconbutton.tsx","./src/shared/ui/input.tsx","./src/shared/ui/maturitybadge.tsx","./src/shared/ui/modal.tsx","./src/shared/ui/select.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/statcard.tsx","./src/shared/ui/toast.tsx","./src/shared/ui/toastcontext.ts","./src/shared/ui/index.ts","./src/shared/ui/usetoast.ts","./src/shared/utils/formatters.ts","./src/stores/demo.ts","./src/stores/index.ts","./src/stores/navigation.ts","./src/stores/theme.ts","./src/test/setup.ts"],"version":"6.0.3"} \ No newline at end of file