diff --git a/reference-customer/plugin-printing-shop/build.gradle.kts b/reference-customer/plugin-printing-shop/build.gradle.kts new file mode 100644 index 0000000..44b218c --- /dev/null +++ b/reference-customer/plugin-printing-shop/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.kotlin.jvm) +} + +description = "vibe_erp reference plug-in: a hello-world printing-shop customer expressed entirely through api.v1." + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + +kotlin { + jvmToolchain(21) +} + +// CRITICAL: this plug-in may depend on api/api-v1 ONLY. The root +// build.gradle.kts enforces this; adding `:platform:*` or `:pbc:*` here +// will fail the build with an architectural-violation error. +// +// This restriction is the entire point of having a "reference plug-in" +// in the repo: it is the executable proof that the framework can express +// a real customer's workflow using only the public plug-in API. If this +// plug-in needs more than api.v1 offers, the right answer is to grow +// api.v1 deliberately, NOT to reach into platform internals. +dependencies { + implementation(project(":api:api-v1")) + implementation(libs.kotlin.stdlib) + compileOnly(libs.pf4j) // for @Extension annotations; provided by host at runtime +} + +// Package this as a "fat plug-in JAR" — PF4J expects a single JAR with the +// plug-in's manifest in META-INF and all of its (non-host) dependencies +// inside it. Since this plug-in only depends on api.v1 (which is provided by +// the host) and the Kotlin stdlib (also provided), the resulting JAR is tiny. +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes( + "Plugin-Id" to "printing-shop", + "Plugin-Version" to project.version, + "Plugin-Provider" to "vibe_erp reference", + "Plugin-Class" to "org.vibeerp.reference.printingshop.PrintingShopPlugin", + "Plugin-Description" to "Reference printing-shop customer plug-in", + "Plugin-Requires" to "1.x", + ) + } + // src/main/resources is already on the resource path automatically; no + // need for an explicit `from(...)` block — that was causing the + // "duplicate plugin.yml" error because Gradle would try to copy it twice. +} diff --git a/reference-customer/plugin-printing-shop/src/main/kotlin/org/vibeerp/reference/printingshop/PrintingShopPlugin.kt b/reference-customer/plugin-printing-shop/src/main/kotlin/org/vibeerp/reference/printingshop/PrintingShopPlugin.kt new file mode 100644 index 0000000..6612bf9 --- /dev/null +++ b/reference-customer/plugin-printing-shop/src/main/kotlin/org/vibeerp/reference/printingshop/PrintingShopPlugin.kt @@ -0,0 +1,47 @@ +package org.vibeerp.reference.printingshop + +import org.vibeerp.api.v1.plugin.Plugin +import org.vibeerp.api.v1.plugin.PluginContext + +/** + * The reference printing-shop plug-in. + * + * This class is the **executable acceptance test** for the entire vibe_erp + * framework. If this plug-in cannot express the workflows in + * `raw/业务流程设计文档/` using ONLY `org.vibeerp.api.v1.*`, the framework's + * extension surface is wrong and must be reshaped — see CLAUDE.md guardrail #1 + * and the architecture spec section 13 ("Verification"). + * + * What this v0.1 incarnation actually does: + * 1. Logs `started` / `stopped` so the operator can confirm the loader + * picked it up. + * 2. Holds onto the [PluginContext] so future versions can publish events, + * register `TaskHandler`s, and register HTTP endpoints. + * + * What it does NOT yet do (deferred to v0.2 of the reference plug-in): + * • register a workflow task handler for "compute job card from quote" + * • register HTTP endpoints under /api/v1/plugins/printing-shop/... + * • subscribe to OrderCreated events from pbc-orders-sales + * • register custom permission checks + * + * Each of those deferred items maps to growing `api.v1` precisely once for a + * real reason — exactly the discipline the architecture demands. + */ +class PrintingShopPlugin : Plugin { + + private var context: PluginContext? = null + + override fun id(): String = "printing-shop" + + override fun version(): String = "0.1.0-SNAPSHOT" + + override fun start(context: PluginContext) { + this.context = context + context.logger.info("printing-shop plug-in started — reference acceptance test active") + } + + override fun stop() { + context?.logger?.info("printing-shop plug-in stopped") + context = null + } +} diff --git a/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_en-US.properties b/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_en-US.properties new file mode 100644 index 0000000..6b4d745 --- /dev/null +++ b/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_en-US.properties @@ -0,0 +1,13 @@ +# Reference printing-shop plug-in — English (US) message bundle. +# +# Keys follow the convention .. declared in +# CLAUDE.md guardrail #6 / architecture spec section 7. The plug-in id +# prefix is critical: it stops two plug-ins from accidentally colliding +# on a generic key like "form.title". + +printingshop.plate.title=Plate +printingshop.plate.create=Create plate +printingshop.plate.approve=Approve plate +printingshop.job.title=Job card +printingshop.job.dispatch=Dispatch to press +printingshop.workflow.quoteToJobCard.name=Quote to job card diff --git a/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_zh-CN.properties b/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_zh-CN.properties new file mode 100644 index 0000000..a7b5753 --- /dev/null +++ b/reference-customer/plugin-printing-shop/src/main/resources/i18n/messages_zh-CN.properties @@ -0,0 +1,10 @@ +# Reference printing-shop plug-in — Simplified Chinese message bundle. +# UTF-8 encoded; Spring's MessageSource is configured to read .properties +# files as UTF-8 in api.v1 / platform-i18n. + +printingshop.plate.title=印版 +printingshop.plate.create=新建印版 +printingshop.plate.approve=审批印版 +printingshop.job.title=工单 +printingshop.job.dispatch=派工至印刷机 +printingshop.workflow.quoteToJobCard.name=报价转工单 diff --git a/reference-customer/plugin-printing-shop/src/main/resources/plugin.yml b/reference-customer/plugin-printing-shop/src/main/resources/plugin.yml new file mode 100644 index 0000000..907fc71 --- /dev/null +++ b/reference-customer/plugin-printing-shop/src/main/resources/plugin.yml @@ -0,0 +1,43 @@ +# vibe_erp plug-in manifest for the reference printing-shop customer. +# +# This file is what a real customer's plug-in would look like. It is loaded +# by the platform-plugins host, validated, and used to wire the plug-in into +# its own classloader and Spring child context. +# +# The id is dotless and url-safe; it becomes the schema-namespace prefix +# (`plugin_printingshop__*`) and the URL prefix for any plug-in endpoints +# (`/api/v1/plugins/printing-shop/...`). +id: printing-shop +version: 0.1.0-SNAPSHOT + +# Semver range against api.v1 — the host loader rejects this plug-in at +# install time if its api.v1 major doesn't match. "1.x" means "any 1.y.z". +requiresApi: "1.x" + +# Translated names. The host displays the entry matching the operator's +# locale; falls back to en-US. +name: + en-US: Printing Shop (reference customer) + zh-CN: 印刷厂(参考客户) + de-DE: Druckerei (Referenzkunde) + +# Permissions this plug-in declares. They appear in the role editor for +# every tenant that has this plug-in enabled. +permissions: + - printingshop.plate.view + - printingshop.plate.create + - printingshop.plate.approve + - printingshop.job.dispatch + +# Other plug-ins that must be present and started before this one. +dependencies: [] + +# Resources to load from inside the JAR after the plug-in starts. Paths are +# relative to the JAR root and the file extensions tell the host how to +# treat them. +metadata: + - workflows/quote-to-job-card.bpmn + - forms/job-card.form.json + - metadata/seed.yml + - i18n/messages_en-US.properties + - i18n/messages_zh-CN.properties