@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,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Material Category Validation Schema
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const materialCategorySchema = z.object({
|
|
10
|
+
code: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1, "Mã danh mục là bắt buộc")
|
|
13
|
+
.max(50, "Mã danh mục tối đa 50 ký tự")
|
|
14
|
+
.trim(),
|
|
15
|
+
|
|
16
|
+
name: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1, "Tên danh mục là bắt buộc")
|
|
19
|
+
.max(200, "Tên danh mục tối đa 200 ký tự")
|
|
20
|
+
.trim(),
|
|
21
|
+
|
|
22
|
+
description: z
|
|
23
|
+
.string()
|
|
24
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
25
|
+
.optional()
|
|
26
|
+
.or(z.literal(""))
|
|
27
|
+
.transform((val) => val || ""),
|
|
28
|
+
|
|
29
|
+
status: z.preprocess((val) => {
|
|
30
|
+
// Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
|
|
31
|
+
if (typeof val === "boolean") {
|
|
32
|
+
return val ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
33
|
+
}
|
|
34
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
35
|
+
if (val === "Active" || val === "Inactive") {
|
|
36
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
37
|
+
}
|
|
38
|
+
return val;
|
|
39
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type MaterialCategoryInput = z.infer<typeof materialCategorySchema>;
|
|
43
|
+
export const materialCategoryUpdateSchema = materialCategorySchema.partial();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Material Pricing Validation Schema
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - supplierId: Required
|
|
8
|
+
* - materialId: Required
|
|
9
|
+
* - price: Required, > 0
|
|
10
|
+
* - effectiveDate: Required, valid date
|
|
11
|
+
* - applicableWarehouseIds: Optional, array of warehouse IDs
|
|
12
|
+
* - status: Optional, enum (pending/approved/rejected)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const materialPricingSchema = z.object({
|
|
16
|
+
supplierId: z.string().min(1, "Nhà cung cấp là bắt buộc"),
|
|
17
|
+
|
|
18
|
+
materialId: z.string().min(1, "Nguyên vật liệu là bắt buộc"),
|
|
19
|
+
|
|
20
|
+
price: z
|
|
21
|
+
.number()
|
|
22
|
+
.positive("Giá phải lớn hơn 0")
|
|
23
|
+
.min(0.01, "Giá tối thiểu là 0.01"),
|
|
24
|
+
|
|
25
|
+
effectiveDate: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1, "Ngày hiệu lực là bắt buộc")
|
|
28
|
+
.refine((val) => {
|
|
29
|
+
const date = new Date(val);
|
|
30
|
+
return !isNaN(date.getTime());
|
|
31
|
+
}, "Ngày hiệu lực không hợp lệ"),
|
|
32
|
+
|
|
33
|
+
applicableWarehouseIds: z.array(z.string()).optional().default([]),
|
|
34
|
+
|
|
35
|
+
status: z
|
|
36
|
+
.enum(["pending", "approved", "rejected"])
|
|
37
|
+
.optional()
|
|
38
|
+
.default("pending"),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Type inference from schema
|
|
43
|
+
*/
|
|
44
|
+
export type MaterialPricingInput = z.infer<typeof materialPricingSchema>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Partial schema for updates (all fields optional)
|
|
48
|
+
*/
|
|
49
|
+
export const materialPricingUpdateSchema = materialPricingSchema.partial();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Schema for set default supplier
|
|
53
|
+
*/
|
|
54
|
+
export const setDefaultSupplierSchema = z.object({
|
|
55
|
+
materialId: z.string().min(1, "Nguyên vật liệu là bắt buộc"),
|
|
56
|
+
supplierId: z.string().min(1, "Nhà cung cấp là bắt buộc"),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Schema for approval
|
|
61
|
+
*/
|
|
62
|
+
export const approvalSchema = z.object({
|
|
63
|
+
id: z.string().min(1, "ID là bắt buộc"),
|
|
64
|
+
action: z.enum(["approve", "reject"]),
|
|
65
|
+
notes: z.string().optional(),
|
|
66
|
+
effectiveDate: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.refine((val) => {
|
|
70
|
+
if (!val) return true; // Optional
|
|
71
|
+
const date = new Date(val);
|
|
72
|
+
return !isNaN(date.getTime());
|
|
73
|
+
}, "Ngày hiệu lực không hợp lệ"),
|
|
74
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Material Validation Schema
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - code: Required, max 50 chars
|
|
10
|
+
* - name: Required, max 200 chars
|
|
11
|
+
* - uom: Required, must be a valid UOM code
|
|
12
|
+
* - category: Required, must be a valid category code
|
|
13
|
+
* - description: Optional, max 1000 chars
|
|
14
|
+
* - status: Enum (active/inactive)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const materialSchema = z.object({
|
|
18
|
+
code: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, "Mã nguyên vật liệu là bắt buộc")
|
|
21
|
+
.max(50, "Mã nguyên vật liệu tối đa 50 ký tự")
|
|
22
|
+
.trim(),
|
|
23
|
+
|
|
24
|
+
item_name: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1, "Tên nguyên vật liệu là bắt buộc")
|
|
27
|
+
.max(200, "Tên nguyên vật liệu tối đa 200 ký tự")
|
|
28
|
+
.trim(),
|
|
29
|
+
|
|
30
|
+
stock_uom: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, "Đơn vị tính là bắt buộc")
|
|
33
|
+
.max(20, "Đơn vị tính tối đa 20 ký tự"),
|
|
34
|
+
|
|
35
|
+
item_group: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1, "Danh mục là bắt buộc")
|
|
38
|
+
.max(50, "Danh mục tối đa 50 ký tự"),
|
|
39
|
+
|
|
40
|
+
description: z
|
|
41
|
+
.string()
|
|
42
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
43
|
+
.optional()
|
|
44
|
+
.or(z.literal(""))
|
|
45
|
+
.transform((val) => val || ""),
|
|
46
|
+
|
|
47
|
+
status: z.preprocess((val) => {
|
|
48
|
+
// Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
|
|
49
|
+
if (typeof val === "boolean") {
|
|
50
|
+
return val ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
51
|
+
}
|
|
52
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
53
|
+
if (val === "Active" || val === "Inactive") {
|
|
54
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
55
|
+
}
|
|
56
|
+
return val;
|
|
57
|
+
}, z.enum(STATUS_VALUES).optional()),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Type inference from schema
|
|
62
|
+
*/
|
|
63
|
+
export type MaterialInput = z.infer<typeof materialSchema>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Partial schema for updates
|
|
67
|
+
*/
|
|
68
|
+
export const materialUpdateSchema = materialSchema.partial();
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Schema for bulk operations
|
|
72
|
+
*/
|
|
73
|
+
export const materialBulkSchema = z.object({
|
|
74
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 nguyên vật liệu"),
|
|
75
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
76
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const MaterialBaseSchema = z.object({
|
|
4
|
+
code: z.string().min(1).max(64),
|
|
5
|
+
item_name: z.string().min(1).max(255),
|
|
6
|
+
item_group: z.string().min(1).max(128),
|
|
7
|
+
stock_uom: z.string().min(1).max(64),
|
|
8
|
+
description: z.string().max(2000).optional().nullable(),
|
|
9
|
+
status: z.string().optional(),
|
|
10
|
+
// General Info fields
|
|
11
|
+
conversion_uom: z.string().optional().nullable(),
|
|
12
|
+
conversion_rate: z.number().optional().nullable(),
|
|
13
|
+
// Inventory fields
|
|
14
|
+
default_stock_uom: z.string().optional().nullable(),
|
|
15
|
+
valuation_rate: z.number().optional().nullable(),
|
|
16
|
+
over_delivery_allowance: z.number().optional().nullable(),
|
|
17
|
+
over_billing_allowance: z.number().optional().nullable(),
|
|
18
|
+
stock_quantity: z.number().optional().nullable(),
|
|
19
|
+
safety_stock: z.number().optional().nullable(),
|
|
20
|
+
reorder_level: z.number().optional().nullable(),
|
|
21
|
+
lead_time_days: z.number().optional().nullable(),
|
|
22
|
+
allow_alternative_item: z.boolean().optional(),
|
|
23
|
+
is_fixed_asset: z.boolean().optional(),
|
|
24
|
+
// Purchasing fields
|
|
25
|
+
purchase_uom: z.string().optional().nullable(),
|
|
26
|
+
purchase_price: z.number().optional().nullable(),
|
|
27
|
+
supplier_id: z.string().optional().nullable(),
|
|
28
|
+
min_order_qty: z.number().optional().nullable(),
|
|
29
|
+
// Sales fields
|
|
30
|
+
sales_uom: z.string().optional().nullable(),
|
|
31
|
+
sales_price: z.number().optional().nullable(),
|
|
32
|
+
tax_category: z.string().optional().nullable(),
|
|
33
|
+
is_sales_item: z.boolean().optional(),
|
|
34
|
+
has_variants: z.boolean().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const MaterialCreateSchema = MaterialBaseSchema;
|
|
38
|
+
|
|
39
|
+
export const MaterialUpdateSchema = MaterialBaseSchema.partial().extend({
|
|
40
|
+
id: z.string().min(1),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export type MaterialCreateInput = z.infer<typeof MaterialCreateSchema>;
|
|
44
|
+
export type MaterialUpdateInput = z.infer<typeof MaterialUpdateSchema>;
|
|
45
|
+
|
|
46
|
+
export const SupplierDefaultSchema = z.object({
|
|
47
|
+
supplierId: z.string().min(1),
|
|
48
|
+
priceListId: z.string().min(1),
|
|
49
|
+
isDefault: z.boolean().default(true),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export type SupplierDefaultInput = z.infer<typeof SupplierDefaultSchema>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const NewPasswordSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
password: z.string().min(8, {
|
|
6
|
+
message: "Password must contain at least 8 characters",
|
|
7
|
+
}),
|
|
8
|
+
confirmPassword: z.string().min(8, {
|
|
9
|
+
message: "Confirm password must contain at least 8 characters",
|
|
10
|
+
}),
|
|
11
|
+
})
|
|
12
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
13
|
+
message: "Passwords must match",
|
|
14
|
+
path: ["confirmPassword"],
|
|
15
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
* Partner Company Validation Schema
|
|
12
|
+
*
|
|
13
|
+
* Validates partner company form submissions on both client & server.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const dateStringSchema = (fieldLabel: string) =>
|
|
17
|
+
z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1, `${fieldLabel} là bắt buộc`)
|
|
20
|
+
.refine(
|
|
21
|
+
(value) => {
|
|
22
|
+
const date = new Date(value);
|
|
23
|
+
return !Number.isNaN(date.getTime());
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
message: `${fieldLabel} không hợp lệ`,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Base schema without refine for partial updates
|
|
31
|
+
const partnerCompanyBaseSchema = z.object({
|
|
32
|
+
code: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1, "Mã công ty đối tác là bắt buộc")
|
|
35
|
+
.max(50, "Mã công ty đối tác tối đa 50 ký tự")
|
|
36
|
+
.trim(),
|
|
37
|
+
|
|
38
|
+
name: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1, "Tên công ty là bắt buộc")
|
|
41
|
+
.max(200, "Tên công ty tối đa 200 ký tự")
|
|
42
|
+
.trim(),
|
|
43
|
+
|
|
44
|
+
taxCode: z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1, "Mã số thuế là bắt buộc")
|
|
47
|
+
.regex(/^[0-9]{10,13}$/, "Mã số thuế phải là 10-13 chữ số"),
|
|
48
|
+
|
|
49
|
+
email: z
|
|
50
|
+
.string()
|
|
51
|
+
.email("Email không hợp lệ")
|
|
52
|
+
.optional()
|
|
53
|
+
.or(z.literal(""))
|
|
54
|
+
.transform((val) => val || ""),
|
|
55
|
+
|
|
56
|
+
phone: z
|
|
57
|
+
.string()
|
|
58
|
+
.regex(/^[0-9]{10}$/, "Số điện thoại phải là 10 chữ số")
|
|
59
|
+
.optional()
|
|
60
|
+
.or(z.literal(""))
|
|
61
|
+
.transform((val) => val || ""),
|
|
62
|
+
|
|
63
|
+
address: z
|
|
64
|
+
.string()
|
|
65
|
+
.max(500, "Địa chỉ tối đa 500 ký tự")
|
|
66
|
+
.optional()
|
|
67
|
+
.or(z.literal(""))
|
|
68
|
+
.transform((val) => val || ""),
|
|
69
|
+
|
|
70
|
+
description: z
|
|
71
|
+
.string()
|
|
72
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
73
|
+
.optional()
|
|
74
|
+
.or(z.literal(""))
|
|
75
|
+
.transform((val) => val || ""),
|
|
76
|
+
|
|
77
|
+
warehouseIds: z.preprocess((val) => {
|
|
78
|
+
// Nếu là mảng, trả về nguyên vẹn
|
|
79
|
+
if (Array.isArray(val)) {
|
|
80
|
+
return val;
|
|
81
|
+
}
|
|
82
|
+
// Nếu là string rỗng hoặc null/undefined, trả về mảng rỗng
|
|
83
|
+
if (!val || val === "") {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
// Nếu là string, thử parse JSON hoặc split bằng dấu phẩy
|
|
87
|
+
if (typeof val === "string") {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(val);
|
|
90
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
91
|
+
} catch {
|
|
92
|
+
// Nếu không parse được JSON, thử split bằng dấu phẩy
|
|
93
|
+
return val
|
|
94
|
+
.split(",")
|
|
95
|
+
.map((v) => v.trim())
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}, z.array(z.string()).optional().default([])),
|
|
101
|
+
|
|
102
|
+
status: z.preprocess((val) => {
|
|
103
|
+
// Chuyển đổi boolean thành string
|
|
104
|
+
if (typeof val === "boolean") {
|
|
105
|
+
return booleanToStatus(val);
|
|
106
|
+
}
|
|
107
|
+
// Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
|
|
108
|
+
if (val === "Active" || val === "Inactive") {
|
|
109
|
+
return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
|
|
110
|
+
}
|
|
111
|
+
return val;
|
|
112
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
113
|
+
|
|
114
|
+
contractStartDate: dateStringSchema("Ngày bắt đầu hợp đồng"),
|
|
115
|
+
contractEndDate: dateStringSchema("Ngày kết thúc hợp đồng"),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Apply refine to the full schema
|
|
119
|
+
export const partnerCompanySchema = partnerCompanyBaseSchema.refine(
|
|
120
|
+
({ contractStartDate, contractEndDate }) => {
|
|
121
|
+
const start = new Date(contractStartDate);
|
|
122
|
+
const end = new Date(contractEndDate);
|
|
123
|
+
return end.getTime() >= start.getTime();
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
message: "Ngày kết thúc hợp đồng phải sau hoặc bằng ngày bắt đầu",
|
|
127
|
+
path: ["contractEndDate"],
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
export type PartnerCompanyInput = z.infer<typeof partnerCompanySchema>;
|
|
132
|
+
|
|
133
|
+
// Create update schema from base schema (partial) with refine
|
|
134
|
+
export const partnerCompanyUpdateSchema = partnerCompanyBaseSchema
|
|
135
|
+
.partial()
|
|
136
|
+
.refine(
|
|
137
|
+
(data) => {
|
|
138
|
+
if (!data.contractStartDate || !data.contractEndDate) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
const start = new Date(data.contractStartDate);
|
|
142
|
+
const end = new Date(data.contractEndDate);
|
|
143
|
+
return end.getTime() >= start.getTime();
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
message: "Ngày kết thúc hợp đồng phải sau hoặc bằng ngày bắt đầu",
|
|
147
|
+
path: ["contractEndDate"],
|
|
148
|
+
},
|
|
149
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const RegisterSchema = z.object({
|
|
4
|
+
firstName: z
|
|
5
|
+
.string()
|
|
6
|
+
.trim()
|
|
7
|
+
.min(2, { message: "First Name must contain at least 2 characters." })
|
|
8
|
+
.max(50, { message: "First Name must contain at most 50 characters." }),
|
|
9
|
+
lastName: z
|
|
10
|
+
.string()
|
|
11
|
+
.trim()
|
|
12
|
+
.min(2, { message: "Last Name must contain at least 2 characters." })
|
|
13
|
+
.max(50, { message: "Last Name must contain at most 50 characters." }),
|
|
14
|
+
username: z
|
|
15
|
+
.string()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.trim()
|
|
18
|
+
.min(3, { message: "Username must contain at least 3 characters." })
|
|
19
|
+
.max(50, { message: "Username must contain at most 50 characters." }),
|
|
20
|
+
email: z
|
|
21
|
+
.string()
|
|
22
|
+
.email({ message: "Invalid email address" })
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.trim(),
|
|
25
|
+
password: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(8, {
|
|
28
|
+
message: "Password must contain at least 8 characters",
|
|
29
|
+
})
|
|
30
|
+
.regex(/(?=.*[a-zA-Z])/, {
|
|
31
|
+
message: "Password must contain at least one letter.",
|
|
32
|
+
})
|
|
33
|
+
.regex(/(?=.*[0-9])/, {
|
|
34
|
+
message: "Password must contain at least one number.",
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
* Resource Validation Schema
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - code: Required, max 50 chars
|
|
15
|
+
* - name: Required, max 200 chars
|
|
16
|
+
* - group: Optional, max 100 chars
|
|
17
|
+
* - description: Optional, max 1000 chars
|
|
18
|
+
* - status: Enum (active/inactive)
|
|
19
|
+
* - parentCode: Optional, max 50 chars
|
|
20
|
+
* - order: Optional, integer >= 0
|
|
21
|
+
* - type: Optional, max 50 chars
|
|
22
|
+
* - icon: Optional, max 100 chars
|
|
23
|
+
* - path: Optional, max 500 chars
|
|
24
|
+
* - config: Optional, JSON string
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const resourceSchema = z.object({
|
|
28
|
+
code: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1, "Mã tài nguyên là bắt buộc")
|
|
31
|
+
.max(50, "Mã tài nguyên tối đa 50 ký tự")
|
|
32
|
+
.trim(),
|
|
33
|
+
|
|
34
|
+
name: z
|
|
35
|
+
.string()
|
|
36
|
+
.min(1, "Tên tài nguyên là bắt buộc")
|
|
37
|
+
.max(200, "Tên tài nguyên tối đa 200 ký tự")
|
|
38
|
+
.trim(),
|
|
39
|
+
|
|
40
|
+
group: z
|
|
41
|
+
.string()
|
|
42
|
+
.max(100, "Nhóm tối đa 100 ký tự")
|
|
43
|
+
.optional()
|
|
44
|
+
.or(z.literal(""))
|
|
45
|
+
.transform((val) => val || undefined),
|
|
46
|
+
|
|
47
|
+
description: z
|
|
48
|
+
.string()
|
|
49
|
+
.max(1000, "Mô tả tối đa 1000 ký tự")
|
|
50
|
+
.optional()
|
|
51
|
+
.or(z.literal(""))
|
|
52
|
+
.transform((val) => val || undefined),
|
|
53
|
+
|
|
54
|
+
status: z.preprocess((val) => {
|
|
55
|
+
// Chuyển đổi boolean thành string
|
|
56
|
+
if (typeof val === "boolean") {
|
|
57
|
+
return booleanToStatus(val);
|
|
58
|
+
}
|
|
59
|
+
// Nếu là string hợp lệ, giữ nguyên (hỗ trợ cả Active/Inactive cũ)
|
|
60
|
+
if (
|
|
61
|
+
val === STATUS_ACTIVE ||
|
|
62
|
+
val === STATUS_INACTIVE ||
|
|
63
|
+
val === "Active" ||
|
|
64
|
+
val === "Inactive"
|
|
65
|
+
) {
|
|
66
|
+
return val === "Active"
|
|
67
|
+
? STATUS_ACTIVE
|
|
68
|
+
: val === "Inactive"
|
|
69
|
+
? STATUS_INACTIVE
|
|
70
|
+
: val;
|
|
71
|
+
}
|
|
72
|
+
// Mặc định là 'active'
|
|
73
|
+
return STATUS_ACTIVE;
|
|
74
|
+
}, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
|
|
75
|
+
|
|
76
|
+
parentCode: z
|
|
77
|
+
.string()
|
|
78
|
+
.max(50, "Mã cha tối đa 50 ký tự")
|
|
79
|
+
.optional()
|
|
80
|
+
.or(z.literal(""))
|
|
81
|
+
.transform((val) => val || undefined),
|
|
82
|
+
|
|
83
|
+
order: z
|
|
84
|
+
.number()
|
|
85
|
+
.int("Thứ tự phải là số nguyên")
|
|
86
|
+
.min(0, "Thứ tự phải >= 0")
|
|
87
|
+
.optional(),
|
|
88
|
+
|
|
89
|
+
type: z
|
|
90
|
+
.string()
|
|
91
|
+
.max(50, "Loại tối đa 50 ký tự")
|
|
92
|
+
.optional()
|
|
93
|
+
.or(z.literal(""))
|
|
94
|
+
.transform((val) => val || undefined),
|
|
95
|
+
|
|
96
|
+
icon: z
|
|
97
|
+
.string()
|
|
98
|
+
.max(100, "Icon tối đa 100 ký tự")
|
|
99
|
+
.optional()
|
|
100
|
+
.or(z.literal(""))
|
|
101
|
+
.transform((val) => val || undefined),
|
|
102
|
+
|
|
103
|
+
path: z
|
|
104
|
+
.string()
|
|
105
|
+
.max(500, "Đường dẫn tối đa 500 ký tự")
|
|
106
|
+
.optional()
|
|
107
|
+
.or(z.literal(""))
|
|
108
|
+
.transform((val) => val || undefined),
|
|
109
|
+
|
|
110
|
+
config: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.or(z.literal(""))
|
|
114
|
+
.transform((val) => val || undefined),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Type inference from schema
|
|
119
|
+
*/
|
|
120
|
+
export type ResourceInput = z.infer<typeof resourceSchema>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Partial schema for updates (all fields optional)
|
|
124
|
+
*/
|
|
125
|
+
export const resourceUpdateSchema = resourceSchema.partial();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Schema for bulk operations
|
|
129
|
+
*/
|
|
130
|
+
export const resourceBulkSchema = z.object({
|
|
131
|
+
ids: z.array(z.string()).min(1, "Phải chọn ít nhất 1 tài nguyên"),
|
|
132
|
+
action: z.enum(["delete", "activate", "deactivate"]),
|
|
133
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const roleSchema = z.object({
|
|
4
|
+
name: z.string().min(1, "Name is required"),
|
|
5
|
+
code: z.string().min(1, "Code is required"),
|
|
6
|
+
description: z.string().optional(),
|
|
7
|
+
status: z.enum(["active", "inactive"]).default("active"),
|
|
8
|
+
permissions: z.array(z.string()).optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type RoleFormData = z.infer<typeof roleSchema>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const SignInSchema = z.object({
|
|
4
|
+
email: z
|
|
5
|
+
.string()
|
|
6
|
+
.email({ message: "Invalid email address" })
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.trim(),
|
|
9
|
+
password: z
|
|
10
|
+
.string()
|
|
11
|
+
.min(8, {
|
|
12
|
+
message: "Password must contain at least 8 characters",
|
|
13
|
+
})
|
|
14
|
+
.max(250, {
|
|
15
|
+
message: "Password must contain at most 250 characters",
|
|
16
|
+
})
|
|
17
|
+
.regex(/(?=.*[a-zA-Z])/, {
|
|
18
|
+
message: "Password must contain at least one letter.",
|
|
19
|
+
})
|
|
20
|
+
.regex(/(?=.*[0-9])/, {
|
|
21
|
+
message: "Password must contain at least one number.",
|
|
22
|
+
}),
|
|
23
|
+
rememberMe: z.boolean().optional(),
|
|
24
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const supplierPricingSchema = z.object({
|
|
4
|
+
materialId: z.string().min(1, "Material ID is required"),
|
|
5
|
+
supplierId: z.string().min(1, "Supplier ID is required"),
|
|
6
|
+
isDefault: z.boolean().default(false),
|
|
7
|
+
priceListId: z.string().optional().nullable(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const updateSupplierPricingSchema = supplierPricingSchema.partial();
|
|
11
|
+
|
|
12
|
+
export type SupplierPricingInput = z.infer<typeof supplierPricingSchema>;
|
|
13
|
+
export type UpdateSupplierPricingInput = z.infer<
|
|
14
|
+
typeof updateSupplierPricingSchema
|
|
15
|
+
>;
|