Skip to Content
DocsComplete Example — Customer entity

Complete Example — Customer entity

A complete entity using all the covered annotations.

This example intentionally combines many features in one place. In a real project, split handlers, enums, and form models into separate files.

Tabs

enum class CustomerTab( override val label: String, override val icon: String, override val order: Int ) : PortalTab { BASIC("Basic Info", "user", 0), CONTACT("Contact", "phone", 1), FINANCIAL("Financial","dollar-sign", 2) }

Enums

enum class CustomerType(val label: String) { NEW("New"), REGULAR("Regular"), PREMIUM("Premium"), VIP("VIP"); override fun toString() = label } enum class CustomerTag { VIP, NEW, REGULAR, PREMIUM }

Action form model

data class SendEmailForm( @field:PortalFormField(label = "Subject", renderer = RendererType.TEXT, required = true, order = 1) val subject: String = "", @field:PortalFormField(label = "Body", renderer = RendererType.TEXTAREA, required = true, order = 2) val body: String = "" )

Action handlers

@ApplicationScoped @io.quarkus.arc.Unremovable class ActivateCustomerHandler { val actionName = "activate" suspend fun validate(entity: EntityData, formData: EntityData?) = if (entity["isActive"] as? Boolean == true) "Customer is already active" else null suspend fun execute(entity: EntityData, formData: EntityData?) = ActionResult.Success("Customer activated.", refreshTable = true) } @ApplicationScoped @io.quarkus.arc.Unremovable class SendEmailHandler { val actionName = "sendEmail" suspend fun validate(entity: EntityData, formData: EntityData?) = null suspend fun execute(entity: EntityData, formData: EntityData?): ActionResult { val subject = formData?.get("subject") as? String ?: "" val email = entity["email"] as? String ?: "" return ActionResult.Success("Email '$subject' sent to $email.") } suspend fun executeBulk(entities: List<EntityData>, formData: EntityData?) = ActionResult.Success("Email sent to ${entities.size} customers.") }

Customer entity

@Entity @Table(name = "customer") @PortalEntity( label = "Customer", module = "CRM", group = "Customers", icon = "users", order = 1, description = "Company customers — main CRM dictionary", tabs = CustomerTab::class, pageSize = 25, softDelete = true, auditLog = true ) @PortalSecurity( viewRoles = ["user", "admin"], editRoles = ["editor", "admin"], deleteRoles = ["admin"], actionRoles = ["editor", "admin"] ) @PortalAction( name = "activate", label = "Activate", icon = "check-circle", handler = ActivateCustomerHandler::class, confirmMessage = "Activate this customer?", order = 1 ) @PortalAction( name = "sendEmail", label = "Send Email", icon = "mail", handler = SendEmailHandler::class, formModel = SendEmailForm::class, bulkAllowed = true, order = 2, variant = "outline" ) class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @PortalField(label = "ID", tab = "BASIC", order = 0, readonly = true, showInFilter = false) var id: Long = 0 @Column(length = 100, nullable = false) @PortalField( label = "Full Name", tab = "BASIC", order = 1, required = true, renderer = RendererType.TEXT, filterType = FilterType.CONTAINS ) var name: String = "" @Column(length = 20) @Enumerated(EnumType.STRING) @PortalField( label = "Customer Type", tab = "BASIC", order = 2, renderer = RendererType.SELECT, filterType = FilterType.IN, selectEnum = CustomerType::class ) var customerType: CustomerType? = null @Column @PortalField( label = "Active", tab = "BASIC", order = 3, renderer = RendererType.BOOLEAN, filterType = FilterType.BOOLEAN ) var isActive: Boolean = true @Column(unique = true) @PortalField( label = "Email", tab = "CONTACT", order = 1, renderer = RendererType.EMAIL, filterType = FilterType.EXACT ) var email: String = "" @Column(length = 20) @Regex(pattern = """^\+?[\d\s\-]{7,20}$""", message = "Invalid phone number format") @PortalField( label = "Phone", tab = "CONTACT", order = 2, renderer = RendererType.TEXT, filterType = FilterType.STARTS_WITH, placeholder = "+1 …" ) var phone: String = "" @Column @PortalField( label = "Country", tab = "CONTACT", order = 3, renderer = RendererType.RELATION, filterType = FilterType.EXACT, showInTable = false ) @PortalRelation( targetEntity = Country::class, displayFields = ["name", "code"], searchFields = ["name", "code"] ) @PortalLookup(labelField = "name", valueField = "id") var countryId: Long? = null @Column @PortalField( label = "Credit Limit", tab = "FINANCIAL", order = 1, renderer = RendererType.DECIMAL, filterType = FilterType.RANGE, placeholder = "0.00" ) @PortalDependency( field = "customerType", operator = DependencyOperator.EQ, value = "New", max = "5000", message = "New customer — max 5000" ) @PortalDependency( field = "customerType", operator = DependencyOperator.EQ, value = "VIP", min = "5000", max = "500000" ) var creditLimit: Double = 0.0 @Column @PortalField( label = "Tags", tab = "FINANCIAL", order = 2, renderer = RendererType.MULTI_SELECT, filterType = FilterType.IN, selectEnum = CustomerTag::class, showInTable = false ) @PortalDependency( field = "customerType", operator = DependencyOperator.EQ, value = "New", allowedValues = ["NEW"], message = "New customer can only have the NEW tag" ) var tags: String = "" // Soft-delete — required when softDelete = true in @PortalEntity @Column @PortalField(label = "Deleted", hidden = true) var deleted: Boolean = false }
Last updated on