@goplusvn/core 0.1.0 → 0.1.1
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.
- package/package.json +2 -1
- package/src/assets/erp_wallpaper.png +0 -0
- package/src/assets/goeat_logo.png +0 -0
- package/src/audit/audit-manager.ts +139 -0
- package/src/audit/index.ts +11 -0
- package/src/audit/memory-audit-logger.ts +86 -0
- package/src/audit/types.ts +50 -0
- package/src/auth/auth-service.ts +97 -0
- package/src/auth/index.ts +266 -0
- package/src/code-generation/index.ts +69 -0
- package/src/configs/auth-routes.ts +17 -0
- package/src/configs/crud.ts +136 -0
- package/src/configs/data/navigations.ts +781 -0
- package/src/configs/data/oauth-links.ts +10 -0
- package/src/configs/entities/material-categories.config.ts +125 -0
- package/src/configs/i18n.ts +12 -0
- package/src/configs/index.ts +26 -0
- package/src/configs/status.ts +25 -0
- package/src/configs/themes.ts +100 -0
- package/src/crud/components/crud-bulk-actions.tsx +91 -0
- package/src/crud/components/crud-card-view.tsx +241 -0
- package/src/crud/components/crud-context.tsx +122 -0
- package/src/crud/components/crud-delete-dialog.tsx +145 -0
- package/src/crud/components/crud-dialog.tsx +406 -0
- package/src/crud/components/crud-empty-state.tsx +104 -0
- package/src/crud/components/crud-export-button.tsx +170 -0
- package/src/crud/components/crud-field-renderer.tsx +653 -0
- package/src/crud/components/crud-filter-chips.tsx +102 -0
- package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
- package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
- package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
- package/src/crud/components/crud-filters/index.tsx +76 -0
- package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
- package/src/crud/components/crud-filters/select-filter.tsx +141 -0
- package/src/crud/components/crud-filters/text-filter.tsx +86 -0
- package/src/crud/components/crud-form.tsx +642 -0
- package/src/crud/components/crud-import-dialog.tsx +440 -0
- package/src/crud/components/crud-infinite-scroll.tsx +116 -0
- package/src/crud/components/crud-page.tsx +1017 -0
- package/src/crud/components/crud-provider.tsx +277 -0
- package/src/crud/components/crud-row-actions.tsx +189 -0
- package/src/crud/components/crud-search.tsx +82 -0
- package/src/crud/components/crud-sheet.tsx +336 -0
- package/src/crud/components/crud-table-skeleton.tsx +26 -0
- package/src/crud/components/crud-table-toolbar.tsx +91 -0
- package/src/crud/components/crud-table.tsx +352 -0
- package/src/crud/components/crud-virtual-table.tsx +55 -0
- package/src/crud/components/index.tsx +20 -0
- package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
- package/src/crud/crud-filters/datetime-filter.tsx +82 -0
- package/src/crud/crud-filters/filter-builder.tsx +64 -0
- package/src/crud/crud-filters/index.tsx +78 -0
- package/src/crud/crud-filters/radio-filter.tsx +79 -0
- package/src/crud/crud-filters/select-filter.tsx +148 -0
- package/src/crud/crud-filters/text-filter.tsx +81 -0
- package/src/crud/index.ts +43 -0
- package/src/crud/lib/crud-service.test.ts +334 -0
- package/src/crud/lib/crud-service.ts +358 -0
- package/src/crud/lib/crud-utils.test.ts +354 -0
- package/src/crud/lib/crud-utils.ts +299 -0
- package/src/crud/lib/crud-validator.ts +247 -0
- package/src/crud/lib/data-loader.ts +234 -0
- package/src/crud/lib/field-calculator.ts +241 -0
- package/src/crud/lib/field-formatter.ts +240 -0
- package/src/crud/lib/import-export-service.test.ts +290 -0
- package/src/crud/lib/import-export-service.ts +352 -0
- package/src/crud/lib/import-server-utils.ts +109 -0
- package/src/crud/lib/lazy-loader.ts +241 -0
- package/src/crud/lib/parse-filters.ts +85 -0
- package/src/crud/lib/permissions.ts +52 -0
- package/src/crud/lib/serialize-config.ts +60 -0
- package/src/crud/lib/stream-loader.ts +145 -0
- package/src/crud/lib/translate-config.ts +335 -0
- package/src/crud/lib/types.ts +11 -0
- package/src/crud/pages/entity-crud-page.tsx +144 -0
- package/src/crud/server.ts +8 -0
- package/src/home/constants.tsx +142 -0
- package/src/home/feature-showcase.tsx +171 -0
- package/src/home/home-page.tsx +191 -0
- package/src/home/hooks/index.ts +1 -0
- package/src/home/hooks/useWidgetPreferences.ts +167 -0
- package/src/home/index.ts +33 -0
- package/src/home/quick-access-dialog.tsx +271 -0
- package/src/home/quick-access-menu.tsx +267 -0
- package/src/home/types.ts +140 -0
- package/src/home/welcome-card.tsx +92 -0
- package/src/home/widget-container.tsx +258 -0
- package/src/home/widgets/base-widget.tsx +200 -0
- package/src/home/widgets/customers-widget.tsx +74 -0
- package/src/home/widgets/index.ts +6 -0
- package/src/home/widgets/orders-widget.tsx +87 -0
- package/src/home/widgets/revenue-widget.tsx +71 -0
- package/src/home/widgets/stock-widget.tsx +109 -0
- package/src/hooks/index.tsx +598 -0
- package/src/hooks/use-tenant.test.tsx +30 -0
- package/src/hooks/use-tenant.ts +5 -0
- package/src/index.ts +17 -0
- package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
- package/src/infrastructure/api-service.ts +317 -0
- package/src/infrastructure/cache/cache-manager.ts +107 -0
- package/src/infrastructure/cache/cache.ts +120 -0
- package/src/infrastructure/cache/index.ts +8 -0
- package/src/infrastructure/cache/types.ts +48 -0
- package/src/infrastructure/cron/cron-manager.ts +239 -0
- package/src/infrastructure/cron/index.ts +6 -0
- package/src/infrastructure/cron/types.ts +41 -0
- package/src/infrastructure/event-bus/event-bus.ts +145 -0
- package/src/infrastructure/event-bus/index.ts +2 -0
- package/src/infrastructure/event-bus/types.ts +22 -0
- package/src/infrastructure/index.ts +32 -0
- package/src/infrastructure/lock/decorators.ts +67 -0
- package/src/infrastructure/lock/index.ts +2 -0
- package/src/infrastructure/lock/lock-manager.ts +33 -0
- package/src/infrastructure/logger/index.ts +2 -0
- package/src/infrastructure/logger/logger.ts +96 -0
- package/src/infrastructure/logger/types.ts +25 -0
- package/src/layout/index.tsx +185 -0
- package/src/navigation/index.ts +91 -0
- package/src/notification/index.ts +14 -0
- package/src/notification/notification-service.ts +120 -0
- package/src/notification/storage/in-memory.ts +56 -0
- package/src/notification/storage/index.ts +1 -0
- package/src/notification/types.ts +51 -0
- package/src/organization/branch-service.ts +299 -0
- package/src/organization/branches.config.ts +154 -0
- package/src/organization/index.ts +5 -0
- package/src/plugin/apps-registry.ts +97 -0
- package/src/plugin/index.ts +5 -0
- package/src/plugin/types.ts +41 -0
- package/src/providers/index.tsx +109 -0
- package/src/providers/tenant-provider.tsx +45 -0
- package/src/rbac/components/roles/role-card.tsx +158 -0
- package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
- package/src/rbac/components/roles/role-toolbar.tsx +123 -0
- package/src/rbac/hooks/use-role-operations.ts +159 -0
- package/src/rbac/hooks/use-roles-data.ts +59 -0
- package/src/rbac/index.ts +297 -0
- package/src/rbac/lib/permission-helpers.ts +63 -0
- package/src/rbac/pages/action-list-page.tsx +25 -0
- package/src/rbac/pages/resource-list-page.tsx +25 -0
- package/src/rbac/pages/role-list-page.tsx +378 -0
- package/src/rbac/permission-service.ts +140 -0
- package/src/rbac/permissions.ts +135 -0
- package/src/rbac/resource-service.ts +115 -0
- package/src/rbac/resource-validator.ts +119 -0
- package/src/rbac/role-service.ts +165 -0
- package/src/rbac/server.ts +16 -0
- package/src/rbac/types.ts +38 -0
- package/src/schemas/action.schema.ts +66 -0
- package/src/schemas/branch.schema.ts +52 -0
- package/src/schemas/coming-soon-schema.ts +9 -0
- package/src/schemas/company.schema.ts +44 -0
- package/src/schemas/forgot-passward-schema.ts +9 -0
- package/src/schemas/index.ts +30 -0
- package/src/schemas/material-category.schema.ts +43 -0
- package/src/schemas/material-pricing.schema.ts +74 -0
- package/src/schemas/material.schema.ts +76 -0
- package/src/schemas/materials.ts +52 -0
- package/src/schemas/new-passward-schema.ts +15 -0
- package/src/schemas/partner-company.schema.ts +149 -0
- package/src/schemas/register-schema.ts +36 -0
- package/src/schemas/resource.schema.ts +133 -0
- package/src/schemas/role.schema.ts +11 -0
- package/src/schemas/sign-in-schema.ts +24 -0
- package/src/schemas/supplier-pricing.schema.ts +15 -0
- package/src/schemas/supplier.schema.ts +120 -0
- package/src/schemas/system-category-group.schema.ts +67 -0
- package/src/schemas/system-category.schema.ts +77 -0
- package/src/schemas/system-config.schema.ts +118 -0
- package/src/schemas/uom.schema.ts +75 -0
- package/src/schemas/user-supplier.schema.ts +179 -0
- package/src/schemas/user.schema.ts +18 -0
- package/src/schemas/verify-email-schema.ts +9 -0
- package/src/schemas/warehouse.schema.ts +49 -0
- package/src/system/components/categories/category-list.tsx +529 -0
- package/src/system/components/categories/category-manager.tsx +89 -0
- package/src/system/components/categories/group-sidebar.tsx +308 -0
- package/src/system/components/settings/setting-dialogs.tsx +197 -0
- package/src/system/components/settings/setting-field.tsx +291 -0
- package/src/system/components/settings/setting-form-dialog.tsx +308 -0
- package/src/system/components/settings/settings-groups.ts +80 -0
- package/src/system/components/settings/settings-search.tsx +71 -0
- package/src/system/components/settings/settings-section.tsx +74 -0
- package/src/system/components/settings/settings-sidebar.tsx +81 -0
- package/src/system/constants.ts +3 -0
- package/src/system/index.ts +150 -0
- package/src/system/job-manager.ts +176 -0
- package/src/system/pages/components/categories/category-list.tsx +537 -0
- package/src/system/pages/components/categories/category-manager.tsx +90 -0
- package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
- package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
- package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
- package/src/system/pages/components/settings/setting-field.tsx +292 -0
- package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
- package/src/system/pages/components/settings/settings-groups.ts +87 -0
- package/src/system/pages/components/settings/settings-page.tsx +372 -0
- package/src/system/pages/components/settings/settings-search.tsx +71 -0
- package/src/system/pages/components/settings/settings-section.tsx +74 -0
- package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
- package/src/system/pages/components/settings/system-settings.tsx +244 -0
- package/src/system/pages/system-category-page.tsx +15 -0
- package/src/system/pages/system-settings-page.tsx +380 -0
- package/src/system/schemas/system-category-group.schema.ts +46 -0
- package/src/system/schemas/system-category.schema.ts +56 -0
- package/src/system/services/settings-service.ts +127 -0
- package/src/system/services/system-category-service.ts +63 -0
- package/src/system/types.ts +45 -0
- package/src/types/index.ts +703 -0
- package/src/ui/auth/auth-layout.tsx +135 -0
- package/src/ui/auth/forgot-password-form.tsx +98 -0
- package/src/ui/auth/index.tsx +7 -0
- package/src/ui/auth/new-password-form.tsx +107 -0
- package/src/ui/auth/oauth-links.tsx +30 -0
- package/src/ui/auth/register-form.tsx +202 -0
- package/src/ui/auth/sign-in-form.tsx +238 -0
- package/src/ui/auth/verify-email-form.tsx +104 -0
- package/src/ui/crud/index.tsx +10 -0
- package/src/ui/data-display/accordion.tsx +65 -0
- package/src/ui/data-display/aspect-ratio.tsx +11 -0
- package/src/ui/data-display/avatar.tsx +163 -0
- package/src/ui/data-display/bento-grid.tsx +77 -0
- package/src/ui/data-display/carousel.tsx +249 -0
- package/src/ui/data-display/chart.tsx +363 -0
- package/src/ui/data-display/code-block-highlight.tsx +54 -0
- package/src/ui/data-display/collapsible.tsx +42 -0
- package/src/ui/data-display/compact-stat-bar.tsx +149 -0
- package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
- package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
- package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
- package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
- package/src/ui/data-display/data-table/data-table.tsx +597 -0
- package/src/ui/data-display/data-table/index.ts +44 -0
- package/src/ui/data-display/data-table-column-header.tsx +75 -0
- package/src/ui/data-display/data-table-pagination.tsx +130 -0
- package/src/ui/data-display/data-table-view-options.tsx +59 -0
- package/src/ui/data-display/formatted-number-input.tsx +210 -0
- package/src/ui/data-display/highlight.tsx +20 -0
- package/src/ui/data-display/hover-card.tsx +48 -0
- package/src/ui/data-display/index.tsx +50 -0
- package/src/ui/data-display/iphone-15-pro.tsx +114 -0
- package/src/ui/data-display/kanban/index.ts +4 -0
- package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
- package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
- package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
- package/src/ui/data-display/kanban/kanban-types.ts +21 -0
- package/src/ui/data-display/kpi-card.tsx +68 -0
- package/src/ui/data-display/media-grid.tsx +110 -0
- package/src/ui/data-display/safari.tsx +175 -0
- package/src/ui/data-display/show-more-text.tsx +55 -0
- package/src/ui/data-display/tabs.tsx +68 -0
- package/src/ui/data-display/timeline.tsx +256 -0
- package/src/ui/feedback/alert.tsx +60 -0
- package/src/ui/feedback/context-menu.tsx +245 -0
- package/src/ui/feedback/drawer.tsx +132 -0
- package/src/ui/feedback/error-dialog.tsx +273 -0
- package/src/ui/feedback/index.tsx +183 -0
- package/src/ui/feedback/progress.tsx +32 -0
- package/src/ui/feedback/sheet.tsx +148 -0
- package/src/ui/feedback/sonner.tsx +36 -0
- package/src/ui/forms/command.tsx +157 -0
- package/src/ui/forms/date-picker.tsx +73 -0
- package/src/ui/forms/date-range-picker.tsx +76 -0
- package/src/ui/forms/date-time-picker.tsx +109 -0
- package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
- package/src/ui/forms/editor/index.tsx +130 -0
- package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
- package/src/ui/forms/emoji-picker.tsx +109 -0
- package/src/ui/forms/file-dropzone.tsx +169 -0
- package/src/ui/forms/file-thumbnail.tsx +29 -0
- package/src/ui/forms/index.tsx +201 -0
- package/src/ui/forms/input-file.tsx +99 -0
- package/src/ui/forms/input-group.tsx +46 -0
- package/src/ui/forms/input-otp.tsx +81 -0
- package/src/ui/forms/input-phone.tsx +172 -0
- package/src/ui/forms/input-spin.tsx +116 -0
- package/src/ui/forms/input-tags.tsx +219 -0
- package/src/ui/forms/input-time.tsx +42 -0
- package/src/ui/forms/multi-select.tsx +629 -0
- package/src/ui/forms/multiple-date-picker.tsx +74 -0
- package/src/ui/forms/radio-group.tsx +42 -0
- package/src/ui/forms/rating.tsx +158 -0
- package/src/ui/forms/time-picker.tsx +57 -0
- package/src/ui/index.tsx +17 -0
- package/src/ui/layout/animated-list.tsx +77 -0
- package/src/ui/layout/animated-sidebar.tsx +294 -0
- package/src/ui/layout/command-menu.tsx +355 -0
- package/src/ui/layout/customizer.tsx +324 -0
- package/src/ui/layout/footer.tsx +43 -0
- package/src/ui/layout/full-screen-toggle.tsx +52 -0
- package/src/ui/layout/header-breadcrumb.tsx +77 -0
- package/src/ui/layout/horizontal-layout-header.tsx +83 -0
- package/src/ui/layout/horizontal-layout.tsx +50 -0
- package/src/ui/layout/index.tsx +25 -0
- package/src/ui/layout/language-dropdown.tsx +103 -0
- package/src/ui/layout/logo.tsx +63 -0
- package/src/ui/layout/main-layout.tsx +57 -0
- package/src/ui/layout/mode-dropdown.tsx +58 -0
- package/src/ui/layout/notification-dropdown.tsx +127 -0
- package/src/ui/layout/page-tabs.tsx +306 -0
- package/src/ui/layout/route-cache.tsx +214 -0
- package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
- package/src/ui/layout/sidebar.tsx +279 -0
- package/src/ui/layout/tab-content-cache.tsx +201 -0
- package/src/ui/layout/tab-navigation-provider.tsx +536 -0
- package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
- package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
- package/src/ui/layout/user-dropdown.tsx +188 -0
- package/src/ui/layout/vertical-layout-header.tsx +65 -0
- package/src/ui/layout/vertical-layout.tsx +47 -0
- package/src/ui/management/audit-log-page.tsx +209 -0
- package/src/ui/management/cache-management.tsx +349 -0
- package/src/ui/management/index.ts +3 -0
- package/src/ui/management/job-management.tsx +308 -0
- package/src/ui/pages/not-found.tsx +30 -0
- package/src/ui/primitives/badge.tsx +66 -0
- package/src/ui/primitives/breadcrumb.tsx +103 -0
- package/src/ui/primitives/button.tsx +129 -0
- package/src/ui/primitives/calendar.tsx +74 -0
- package/src/ui/primitives/card.tsx +86 -0
- package/src/ui/primitives/checkbox.tsx +31 -0
- package/src/ui/primitives/client.ts +30 -0
- package/src/ui/primitives/combobox.tsx +290 -0
- package/src/ui/primitives/dialog.tsx +121 -0
- package/src/ui/primitives/dropdown-menu.tsx +239 -0
- package/src/ui/primitives/dynamic-icon.tsx +24 -0
- package/src/ui/primitives/index.tsx +134 -0
- package/src/ui/primitives/input-number.tsx +131 -0
- package/src/ui/primitives/input.tsx +22 -0
- package/src/ui/primitives/keyboard.tsx +23 -0
- package/src/ui/primitives/label.tsx +24 -0
- package/src/ui/primitives/menubar.tsx +262 -0
- package/src/ui/primitives/navigation-menu.tsx +157 -0
- package/src/ui/primitives/pagination.tsx +118 -0
- package/src/ui/primitives/popover.tsx +56 -0
- package/src/ui/primitives/prefetch-link.tsx +60 -0
- package/src/ui/primitives/resizable.tsx +59 -0
- package/src/ui/primitives/scroll-area.tsx +63 -0
- package/src/ui/primitives/select.tsx +172 -0
- package/src/ui/primitives/separator.tsx +51 -0
- package/src/ui/primitives/sidebar.tsx +844 -0
- package/src/ui/primitives/slider.tsx +27 -0
- package/src/ui/primitives/status-badge.tsx +47 -0
- package/src/ui/primitives/sticky-layout.tsx +50 -0
- package/src/ui/primitives/switch.tsx +29 -0
- package/src/ui/primitives/table.tsx +116 -0
- package/src/ui/primitives/tabs.tsx +55 -0
- package/src/ui/primitives/toggle-group.tsx +70 -0
- package/src/ui/primitives/toggle.tsx +47 -0
- package/src/ui/primitives/tooltip.tsx +59 -0
- package/src/user/components/dangerous-zone.tsx +34 -0
- package/src/user/components/delete-account-form.tsx +40 -0
- package/src/user/components/index.ts +4 -0
- package/src/user/components/profile-info-form.tsx +390 -0
- package/src/user/components/profile-info.tsx +32 -0
- package/src/user/components/unified-profile-dialog.tsx +1019 -0
- package/src/user/components/user-stats.tsx +27 -0
- package/src/user/components/user-toolbar.tsx +137 -0
- package/src/user/components/users-card-view.tsx +253 -0
- package/src/user/index.ts +11 -0
- package/src/user/pages/user-list-page.tsx +234 -0
- package/src/user/pages/users-client-page.tsx +385 -0
- package/src/user/profile-page.tsx +19 -0
- package/src/user/schemas.ts +68 -0
- package/src/user/types.ts +34 -0
- package/src/user/user-service.ts +538 -0
- package/src/utils/index.ts +906 -0
- package/src/workflow/activity-timeline.tsx +412 -0
- package/src/workflow/approval-workflow.tsx +31 -0
- package/src/workflow/index.ts +2 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
STATUS_ACTIVE,
|
|
5
|
+
STATUS_INACTIVE,
|
|
6
|
+
STATUS_VALUES,
|
|
7
|
+
booleanToStatus,
|
|
8
|
+
} from "../configs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Supplier Validation Schema
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - code: Required, max 50 chars
|
|
15
|
+
* - name: Required, max 200 chars
|
|
16
|
+
* - taxCode: Required, 10-13 digits
|
|
17
|
+
* - email: Optional but must be valid if provided
|
|
18
|
+
* - phone: Optional, 10 digits if provided
|
|
19
|
+
* - address: Optional, max 500 chars
|
|
20
|
+
* - description: Optional, max 1000 chars
|
|
21
|
+
* - status: Enum (active/inactive)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const supplierBaseSchema = z.object({
|
|
25
|
+
code: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1, "Mã nhà cung cấp là bắt buộc")
|
|
28
|
+
.max(50, "Mã nhà cung cấp tối đa 50 ký tự")
|
|
29
|
+
.trim(),
|
|
30
|
+
|
|
31
|
+
name: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1, "Tên nhà cung cấp là bắt buộc")
|
|
34
|
+
.max(200, "Tên nhà cung cấp tối đa 200 ký tự")
|
|
35
|
+
.trim(),
|
|
36
|
+
|
|
37
|
+
taxCode: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1, "Mã số thuế là bắt buộc")
|
|
40
|
+
.regex(/^[0-9]{10,13}$/, "Mã số thuế phải là 10-13 chữ số"),
|
|
41
|
+
|
|
42
|
+
email: z.string().email("Email không hợp lệ").optional().or(z.literal("")),
|
|
43
|
+
|
|
44
|
+
phone: z
|
|
45
|
+
.string()
|
|
46
|
+
.regex(/^[0-9]{10}$/, "Số điện thoại phải là 10 chữ số")
|
|
47
|
+
.optional()
|
|
48
|
+
.or(z.literal("")),
|
|
49
|
+
|
|
50
|
+
address: z
|
|
51
|
+
.string()
|
|
52
|
+
.max(500, "Địa chỉ tối đa 500 ký tự")
|
|
53
|
+
.optional()
|
|
54
|
+
.or(z.literal(""))
|
|
55
|
+
.transform((val) => val || ""),
|
|
56
|
+
|
|
57
|
+
description: z
|
|
58
|
+
.string()
|
|
59
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
60
|
+
.optional()
|
|
61
|
+
.or(z.literal(""))
|
|
62
|
+
.transform((val) => val || ""),
|
|
63
|
+
|
|
64
|
+
status: z.preprocess((val) => {
|
|
65
|
+
// Chuyển đổi boolean thành string
|
|
66
|
+
if (typeof val === "boolean") {
|
|
67
|
+
return booleanToStatus(val);
|
|
68
|
+
}
|
|
69
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
70
|
+
if (val === "Active" || val === "Inactive") {
|
|
71
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
72
|
+
}
|
|
73
|
+
return val;
|
|
74
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
75
|
+
|
|
76
|
+
contractStartDate: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.transform((val) => (val ? new Date(val) : null)) // Convert string to Date or null
|
|
80
|
+
.or(z.date().optional())
|
|
81
|
+
.nullable(),
|
|
82
|
+
|
|
83
|
+
contractEndDate: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.transform((val) => (val ? new Date(val) : null)) // Convert string to Date or null
|
|
87
|
+
.or(z.date().optional())
|
|
88
|
+
.nullable(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const supplierSchema = supplierBaseSchema.refine(
|
|
92
|
+
(data) => {
|
|
93
|
+
if (data.contractStartDate && data.contractEndDate) {
|
|
94
|
+
return data.contractEndDate >= data.contractStartDate;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
message: "Ngày kết thúc hợp đồng phải sau ngày bắt đầu",
|
|
100
|
+
path: ["contractEndDate"],
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Type inference from schema
|
|
106
|
+
*/
|
|
107
|
+
export type SupplierInput = z.infer<typeof supplierSchema>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Partial schema for updates (all fields optional)
|
|
111
|
+
*/
|
|
112
|
+
export const supplierUpdateSchema = supplierBaseSchema.partial();
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Schema for bulk operations
|
|
116
|
+
*/
|
|
117
|
+
export const supplierBulkSchema = z.object({
|
|
118
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 nhà cung cấp"),
|
|
119
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
120
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* System Category Group Validation Schema
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - code: Required, max 50 chars
|
|
10
|
+
* - name: Required, max 200 chars
|
|
11
|
+
* - description: Optional, max 1000 chars
|
|
12
|
+
* - status: Enum (active/inactive)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const systemCategoryGroupSchema = z.object({
|
|
16
|
+
code: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1, "Mã nhóm danh mục là bắt buộc")
|
|
19
|
+
.max(50, "Mã nhóm danh mục tối đa 50 ký tự")
|
|
20
|
+
.trim(),
|
|
21
|
+
|
|
22
|
+
name: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1, "Tên nhóm danh mục là bắt buộc")
|
|
25
|
+
.max(200, "Tên nhóm danh mục tối đa 200 ký tự")
|
|
26
|
+
.trim(),
|
|
27
|
+
|
|
28
|
+
description: z
|
|
29
|
+
.string()
|
|
30
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
31
|
+
.optional()
|
|
32
|
+
.or(z.literal(""))
|
|
33
|
+
.transform((val) => val || ""),
|
|
34
|
+
|
|
35
|
+
status: z.preprocess((val) => {
|
|
36
|
+
// Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
|
|
37
|
+
if (typeof val === "boolean") {
|
|
38
|
+
return val ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
39
|
+
}
|
|
40
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
41
|
+
if (val === "Active" || val === "Inactive") {
|
|
42
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
43
|
+
}
|
|
44
|
+
return val;
|
|
45
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Type inference from schema
|
|
50
|
+
*/
|
|
51
|
+
export type SystemCategoryGroupInput = z.infer<
|
|
52
|
+
typeof systemCategoryGroupSchema
|
|
53
|
+
>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Partial schema for updates (all fields optional)
|
|
57
|
+
*/
|
|
58
|
+
export const systemCategoryGroupUpdateSchema =
|
|
59
|
+
systemCategoryGroupSchema.partial();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Schema for bulk operations
|
|
63
|
+
*/
|
|
64
|
+
export const systemCategoryGroupBulkSchema = z.object({
|
|
65
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 nhóm danh mục"),
|
|
66
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
67
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* System Category Validation Schema
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - code: Required, max 50 chars
|
|
10
|
+
* - name: Required, max 200 chars
|
|
11
|
+
* - groupCode: Required, max 50 chars
|
|
12
|
+
* - description: Optional, max 1000 chars
|
|
13
|
+
* - status: Enum (active/inactive)
|
|
14
|
+
* - order: Optional, integer >= 0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const systemCategorySchema = z.object({
|
|
18
|
+
code: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, "Mã danh mục là bắt buộc")
|
|
21
|
+
.max(50, "Mã danh mục tối đa 50 ký tự")
|
|
22
|
+
.trim(),
|
|
23
|
+
|
|
24
|
+
name: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1, "Tên danh mục là bắt buộc")
|
|
27
|
+
.max(200, "Tên danh mục tối đa 200 ký tự")
|
|
28
|
+
.trim(),
|
|
29
|
+
|
|
30
|
+
groupCode: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, "Mã nhóm danh mục là bắt buộc")
|
|
33
|
+
.max(50, "Mã nhóm danh mục tối đa 50 ký tự"),
|
|
34
|
+
|
|
35
|
+
description: z
|
|
36
|
+
.string()
|
|
37
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
38
|
+
.optional()
|
|
39
|
+
.or(z.literal(""))
|
|
40
|
+
.transform((val) => val || ""),
|
|
41
|
+
|
|
42
|
+
status: z.preprocess((val) => {
|
|
43
|
+
// Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
|
|
44
|
+
if (typeof val === "boolean") {
|
|
45
|
+
return val ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
46
|
+
}
|
|
47
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
48
|
+
if (val === "Active" || val === "Inactive") {
|
|
49
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
50
|
+
}
|
|
51
|
+
return val;
|
|
52
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
53
|
+
|
|
54
|
+
order: z
|
|
55
|
+
.number()
|
|
56
|
+
.int("Thứ tự phải là số nguyên")
|
|
57
|
+
.min(0, "Thứ tự phải >= 0")
|
|
58
|
+
.optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Type inference from schema
|
|
63
|
+
*/
|
|
64
|
+
export type SystemCategoryInput = z.infer<typeof systemCategorySchema>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Partial schema for updates (all fields optional)
|
|
68
|
+
*/
|
|
69
|
+
export const systemCategoryUpdateSchema = systemCategorySchema.partial();
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Schema for bulk operations
|
|
73
|
+
*/
|
|
74
|
+
export const systemCategoryBulkSchema = z.object({
|
|
75
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 danh mục"),
|
|
76
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
77
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
STATUS_ACTIVE,
|
|
5
|
+
STATUS_INACTIVE,
|
|
6
|
+
STATUS_VALUES,
|
|
7
|
+
booleanToStatus,
|
|
8
|
+
} from "../configs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SystemConfig Validation Schema
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - key: Required, unique, max 100 chars
|
|
15
|
+
* - value: Required, max 5000 chars
|
|
16
|
+
* - type: Optional, default "string", max 50 chars
|
|
17
|
+
* - category: Optional, default "general", max 100 chars
|
|
18
|
+
* - description: Optional, max 1000 chars
|
|
19
|
+
* - isEncrypted: Optional, default false
|
|
20
|
+
* - isReadOnly: Optional, default false
|
|
21
|
+
* - status: Enum (active/inactive), default "active"
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const systemConfigSchema = z.object({
|
|
25
|
+
key: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1, "Key là bắt buộc")
|
|
28
|
+
.max(100, "Key tối đa 100 ký tự")
|
|
29
|
+
.trim()
|
|
30
|
+
.regex(
|
|
31
|
+
/^[a-zA-Z0-9._-]+$/,
|
|
32
|
+
"Key chỉ được chứa chữ cái, số, dấu chấm, gạch dưới và gạch ngang",
|
|
33
|
+
),
|
|
34
|
+
|
|
35
|
+
value: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1, "Value là bắt buộc")
|
|
38
|
+
.max(5000, "Value tối đa 5000 ký tự")
|
|
39
|
+
.trim(),
|
|
40
|
+
|
|
41
|
+
type: z
|
|
42
|
+
.string()
|
|
43
|
+
.max(50, "Type tối đa 50 ký tự")
|
|
44
|
+
.default("string")
|
|
45
|
+
.optional()
|
|
46
|
+
.or(z.literal(""))
|
|
47
|
+
.transform((val) => val || "string"),
|
|
48
|
+
|
|
49
|
+
category: z
|
|
50
|
+
.string()
|
|
51
|
+
.max(100, "Category tối đa 100 ký tự")
|
|
52
|
+
.default("general")
|
|
53
|
+
.optional()
|
|
54
|
+
.or(z.literal(""))
|
|
55
|
+
.transform((val) => val || "general"),
|
|
56
|
+
|
|
57
|
+
description: z
|
|
58
|
+
.string()
|
|
59
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
60
|
+
.optional()
|
|
61
|
+
.or(z.literal(""))
|
|
62
|
+
.transform((val) => val || undefined),
|
|
63
|
+
|
|
64
|
+
isEncrypted: z.boolean().default(false).optional(),
|
|
65
|
+
|
|
66
|
+
isReadOnly: z.boolean().default(false).optional(),
|
|
67
|
+
|
|
68
|
+
status: z.preprocess((val) => {
|
|
69
|
+
// Chuyển đổi boolean thành string
|
|
70
|
+
if (typeof val === "boolean") {
|
|
71
|
+
return booleanToStatus(val);
|
|
72
|
+
}
|
|
73
|
+
// Nếu là string hợp lệ, giữ nguyên (hỗ trợ cả Active/Inactive cũ)
|
|
74
|
+
if (
|
|
75
|
+
val === STATUS_ACTIVE ||
|
|
76
|
+
val === STATUS_INACTIVE ||
|
|
77
|
+
val === "Active" ||
|
|
78
|
+
val === "Inactive"
|
|
79
|
+
) {
|
|
80
|
+
return val === "Active"
|
|
81
|
+
? STATUS_ACTIVE
|
|
82
|
+
: val === "Inactive"
|
|
83
|
+
? STATUS_INACTIVE
|
|
84
|
+
: val;
|
|
85
|
+
}
|
|
86
|
+
// Mặc định là 'active'
|
|
87
|
+
return STATUS_ACTIVE;
|
|
88
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Type inference from schema
|
|
93
|
+
*/
|
|
94
|
+
export type SystemConfigInput = z.infer<typeof systemConfigSchema>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Partial schema for updates (all fields optional except key)
|
|
98
|
+
*/
|
|
99
|
+
export const systemConfigUpdateSchema = systemConfigSchema.partial().extend({
|
|
100
|
+
key: z
|
|
101
|
+
.string()
|
|
102
|
+
.min(1, "Key là bắt buộc")
|
|
103
|
+
.max(100, "Key tối đa 100 ký tự")
|
|
104
|
+
.trim()
|
|
105
|
+
.regex(
|
|
106
|
+
/^[a-zA-Z0-9._-]+$/,
|
|
107
|
+
"Key chỉ được chứa chữ cái, số, dấu chấm, gạch dưới và gạch ngang",
|
|
108
|
+
)
|
|
109
|
+
.optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Schema for bulk operations
|
|
114
|
+
*/
|
|
115
|
+
export const systemConfigBulkSchema = z.object({
|
|
116
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 cấu hình"),
|
|
117
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
118
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unit of Measure (UOM) Validation Schema
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Base schema without refine for partial updates
|
|
10
|
+
const uomBaseSchema = z.object({
|
|
11
|
+
code: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1, "Mã đơn vị tính là bắt buộc")
|
|
14
|
+
.max(50, "Mã đơn vị tính tối đa 50 ký tự")
|
|
15
|
+
.trim(),
|
|
16
|
+
|
|
17
|
+
name: z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1, "Tên đơn vị tính là bắt buộc")
|
|
20
|
+
.max(100, "Tên đơn vị tính tối đa 100 ký tự")
|
|
21
|
+
.trim(),
|
|
22
|
+
|
|
23
|
+
description: z
|
|
24
|
+
.string()
|
|
25
|
+
.max(500, "Mô tả tối đa 500 ký tự")
|
|
26
|
+
.optional()
|
|
27
|
+
.or(z.literal(""))
|
|
28
|
+
.transform((val) => val || ""),
|
|
29
|
+
|
|
30
|
+
status: z.preprocess((val) => {
|
|
31
|
+
// Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
|
|
32
|
+
if (typeof val === "boolean") {
|
|
33
|
+
return val ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
34
|
+
}
|
|
35
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
36
|
+
if (val === "Active" || val === "Inactive") {
|
|
37
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
38
|
+
}
|
|
39
|
+
return val;
|
|
40
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
41
|
+
|
|
42
|
+
baseUomCode: z.preprocess((val) => {
|
|
43
|
+
// Chuyển đổi string rỗng thành null
|
|
44
|
+
if (val === "" || val === null || val === undefined) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return val;
|
|
48
|
+
}, z.string().max(20, "Mã đơn vị quy đổi tối đa 20 ký tự").nullable().optional()),
|
|
49
|
+
|
|
50
|
+
conversionRate: z.preprocess(
|
|
51
|
+
(val) => {
|
|
52
|
+
// Chuyển đổi string rỗng hoặc string number thành number hoặc null
|
|
53
|
+
if (val === "" || val === null || val === undefined) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// Nếu là string, thử parse thành number
|
|
57
|
+
if (typeof val === "string") {
|
|
58
|
+
const parsed = parseFloat(val);
|
|
59
|
+
return isNaN(parsed) ? null : parsed;
|
|
60
|
+
}
|
|
61
|
+
// Nếu đã là number, giữ nguyên
|
|
62
|
+
return val;
|
|
63
|
+
},
|
|
64
|
+
z
|
|
65
|
+
.union([z.number().positive("Tỷ lệ quy đổi phải lớn hơn 0"), z.null()])
|
|
66
|
+
.optional(),
|
|
67
|
+
),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Apply refine to the full schema
|
|
71
|
+
// Export the base schema directly
|
|
72
|
+
export const uomSchema = uomBaseSchema;
|
|
73
|
+
|
|
74
|
+
export type UomInput = z.infer<typeof uomSchema>;
|
|
75
|
+
export const uomUpdateSchema = uomBaseSchema.partial();
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
STATUS_ACTIVE,
|
|
5
|
+
STATUS_INACTIVE,
|
|
6
|
+
STATUS_VALUES,
|
|
7
|
+
booleanToStatus,
|
|
8
|
+
} from "../configs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* UserSupplier Validation Schema
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - userId: Required, must be valid UUID (hệ thống dùng UUID)
|
|
15
|
+
* - supplierId: Required, must be valid UUID (hệ thống dùng UUID)
|
|
16
|
+
* - status: Enum (active/inactive), default active
|
|
17
|
+
* - startDate: Required, must be valid date
|
|
18
|
+
* - endDate: Optional, must be valid date, must be after startDate if provided
|
|
19
|
+
* - role: Optional, max 100 chars
|
|
20
|
+
*
|
|
21
|
+
* Note:
|
|
22
|
+
* - Hệ thống dùng UUID (crypto.randomUUID()) - đây là chuẩn
|
|
23
|
+
* - Tạm thời chấp nhận cả CUID để tương thích với dữ liệu seed cũ
|
|
24
|
+
* - TODO: Migrate dữ liệu seed cũ từ CUID sang UUID và remove CUID support
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate CUID format (tạm thời cho dữ liệu seed cũ)
|
|
29
|
+
* CUID format: starts with 'c', followed by 20-30 alphanumeric characters
|
|
30
|
+
* Example: cmgf9g6ck000yju0yoa07wfzl
|
|
31
|
+
* TODO: Remove sau khi migrate dữ liệu seed
|
|
32
|
+
*/
|
|
33
|
+
const cuidRegex = /^c[a-z0-9]{20,30}$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate UUID format (chuẩn hệ thống)
|
|
37
|
+
* UUID format: 8-4-4-4-12 hexadecimal digits with hyphens
|
|
38
|
+
* Example: 550e8400-e29b-41d4-a716-446655440000
|
|
39
|
+
*/
|
|
40
|
+
const uuidRegex =
|
|
41
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Custom validation for CUID or UUID
|
|
45
|
+
* TODO: Chỉ validate UUID sau khi migrate dữ liệu seed cũ
|
|
46
|
+
*/
|
|
47
|
+
const cuidOrUuid = z
|
|
48
|
+
.string()
|
|
49
|
+
.refine((val) => cuidRegex.test(val) || uuidRegex.test(val), {
|
|
50
|
+
message:
|
|
51
|
+
"ID không hợp lệ (phải là UUID, tạm thời chấp nhận CUID cho dữ liệu cũ)",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Base schema object (before refine) - needed for .partial()
|
|
55
|
+
const userSupplierBaseSchema = z.object({
|
|
56
|
+
userId: z.preprocess(
|
|
57
|
+
(val) => {
|
|
58
|
+
// Convert to string and handle empty values
|
|
59
|
+
if (val === null || val === undefined || val === "") {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return String(val).trim();
|
|
63
|
+
},
|
|
64
|
+
z
|
|
65
|
+
.string({
|
|
66
|
+
required_error: "Người dùng là bắt buộc",
|
|
67
|
+
})
|
|
68
|
+
.min(1, "Người dùng là bắt buộc")
|
|
69
|
+
.pipe(cuidOrUuid),
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
supplierId: z.preprocess(
|
|
73
|
+
(val) => {
|
|
74
|
+
// Convert to string and handle empty values
|
|
75
|
+
if (val === null || val === undefined || val === "") {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return String(val).trim();
|
|
79
|
+
},
|
|
80
|
+
z
|
|
81
|
+
.string({
|
|
82
|
+
required_error: "Nhà cung cấp là bắt buộc",
|
|
83
|
+
})
|
|
84
|
+
.min(1, "Nhà cung cấp là bắt buộc")
|
|
85
|
+
.pipe(cuidOrUuid),
|
|
86
|
+
),
|
|
87
|
+
|
|
88
|
+
status: z.preprocess((val) => {
|
|
89
|
+
// Chuyển đổi boolean thành string
|
|
90
|
+
if (typeof val === "boolean") {
|
|
91
|
+
return booleanToStatus(val);
|
|
92
|
+
}
|
|
93
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
94
|
+
if (val === "Active" || val === "Inactive") {
|
|
95
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
96
|
+
}
|
|
97
|
+
return val;
|
|
98
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
99
|
+
|
|
100
|
+
startDate: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.transform((val) => {
|
|
104
|
+
if (!val) return new Date(); // Default to now if not provided
|
|
105
|
+
const date = new Date(val);
|
|
106
|
+
if (isNaN(date.getTime())) {
|
|
107
|
+
throw new Error("Ngày bắt đầu không hợp lệ");
|
|
108
|
+
}
|
|
109
|
+
return date;
|
|
110
|
+
})
|
|
111
|
+
.or(z.date().default(() => new Date())),
|
|
112
|
+
|
|
113
|
+
endDate: z
|
|
114
|
+
.string()
|
|
115
|
+
.optional()
|
|
116
|
+
.transform((val) => {
|
|
117
|
+
if (!val) return undefined;
|
|
118
|
+
const date = new Date(val);
|
|
119
|
+
if (isNaN(date.getTime())) {
|
|
120
|
+
throw new Error("Ngày kết thúc không hợp lệ");
|
|
121
|
+
}
|
|
122
|
+
return date;
|
|
123
|
+
})
|
|
124
|
+
.or(z.date().optional())
|
|
125
|
+
.nullable(),
|
|
126
|
+
|
|
127
|
+
role: z
|
|
128
|
+
.string()
|
|
129
|
+
.max(100, "Vai trò tối đa 100 ký tự")
|
|
130
|
+
.optional()
|
|
131
|
+
.or(z.literal(""))
|
|
132
|
+
.transform((val) => val || undefined),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Main schema with refine for validation
|
|
136
|
+
export const userSupplierSchema = userSupplierBaseSchema.refine(
|
|
137
|
+
(data) => {
|
|
138
|
+
// Nếu có endDate, phải sau startDate
|
|
139
|
+
if (data.endDate && data.startDate) {
|
|
140
|
+
return data.endDate >= data.startDate;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
message: "Ngày kết thúc phải sau ngày bắt đầu",
|
|
146
|
+
path: ["endDate"],
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Type inference from schema
|
|
152
|
+
*/
|
|
153
|
+
export type UserSupplierInput = z.infer<typeof userSupplierSchema>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Partial schema for updates (all fields optional)
|
|
157
|
+
* Create from base schema before refine to allow .partial()
|
|
158
|
+
*/
|
|
159
|
+
export const userSupplierUpdateSchema = userSupplierBaseSchema.partial().refine(
|
|
160
|
+
(data) => {
|
|
161
|
+
// Nếu có endDate và startDate, phải sau startDate
|
|
162
|
+
if (data.endDate && data.startDate) {
|
|
163
|
+
return data.endDate >= data.startDate;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
message: "Ngày kết thúc phải sau ngày bắt đầu",
|
|
169
|
+
path: ["endDate"],
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Schema for bulk operations
|
|
175
|
+
*/
|
|
176
|
+
export const userSupplierBulkSchema = z.object({
|
|
177
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 bản ghi"),
|
|
178
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
179
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const userSchema = z.object({
|
|
4
|
+
name: z.string().min(1, "Tên hiển thị là bắt buộc"),
|
|
5
|
+
email: z.string().email("Email không hợp lệ"),
|
|
6
|
+
// Support both boolean and status string, normalize to boolean
|
|
7
|
+
isActive: z.preprocess((val) => {
|
|
8
|
+
if (typeof val === "boolean") return val;
|
|
9
|
+
if (val === "active") return true;
|
|
10
|
+
if (val === "inactive") return false;
|
|
11
|
+
return true; // default
|
|
12
|
+
}, z.boolean().default(true)),
|
|
13
|
+
// Also accept status string from form
|
|
14
|
+
status: z.enum(["active", "inactive"]).optional(),
|
|
15
|
+
image: z.string().optional().nullable(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type UserFormValues = z.infer<typeof userSchema>;
|