xiaohongshu
xiaohongshu
moqui-service-writer
This skill should be used when users need to create, validate, or modify Moqui framework services, entities, and queries. It provides comprehensive guidance for writing correct Moqui XML definitions, following framework patterns and conventions.
Full skill instructions
This skill enables writing correct Moqui framework services, entities, and queries by providing comprehensive patterns, validation tools, and reference documentation. It helps developers follow Moqui conventions and avoid common pitfalls when working with the enterprise framework.
scripts/generate_service.py --interactiveassets/entity_template.xml as a starting pointscripts/validate_service.py and scripts/validate_entity.pyscripts/format_moqui_xml.pyreferences/ for best practicesassets/ for new componentsDetermine service purpose and select appropriate verb:
Use service generator:
python3 scripts/generate_service.py --interactive
Or generate specific types:
python3 scripts/generate_service.py --verb create --noun Product --type create
Edit the generated service following patterns in references/service_patterns.md:
python3 scripts/validate_service.py path/to/your/service.xml
Use entity-auto services for standard operations:
<service verb="create" noun="Product" type="entity-auto">
<in-parameters>
<auto-parameters entity-name="com.example.Product" include="nonpk"/>
</in-parameters>
<out-parameters>
<parameter name="productId"/>
</out-parameters>
</service>
Implement find services with filtering:
<service verb="find" noun="Product" allow-remote="true">
<in-parameters>
<parameter name="productTypeEnumId"/>
<parameter name="statusId"/>
<parameter name="orderByField" default-value="productName"/>
</in-parameters>
<out-parameters>
<parameter name="productList" type="List"/>
<parameter name="totalCount" type="Integer"/>
</out-parameters>
<actions>
<entity-find entity-name="com.example.Product" list="productList" count="totalCount">
<econdition field-name="productTypeEnumId" ignore-if-empty="true"/>
<econdition field-name="statusId" ignore-if-empty="true"/>
<order-by field-name="${orderByField}"/>
</entity-find>
</actions>
</service>
Implement complex operations with proper error handling:
Follow Moqui Framework Transaction Patterns:
transaction="force-new" for financial/bulk operationstransaction-timeout for operations > 5 minutestransaction="cache" (causes locking issues in framework)<!-- Standard service - use framework defaults -->
<service verb="process" noun="Order" authenticate="true">
<in-parameters>
<parameter name="orderId" type="id" required="true"/>
</in-parameters>
<actions>
<!-- Validate order exists -->
<entity-find-one entity-name="com.example.Order" value-field="order"/>
<if condition="order == null">
<return error="true" message="Order not found with ID: ${orderId}"/>
</if>
<!-- Check permissions -->
<set field="hasPermission" from="ec.user.hasPermission('ORDER', 'PROCESS')"/>
<if condition="!hasPermission">
<return error="true" message="Permission denied"/>
</if>
<!-- Business logic -->
<service-call name="update#OrderStatus" in-map="[orderId: orderId, statusId: 'OsProcessing']"/>
<service-call name="send#OrderNotification" in-map="[orderId: orderId]"/>
<message public="true" type="success">Order processed successfully</message>
</actions>
</service>
<!-- Critical financial operation - explicit transaction -->
<service verb="close" noun="FinancialPeriod" authenticate="true"
transaction="force-new" transaction-timeout="600">
<in-parameters>
<parameter name="financialPeriodId" type="id" required="true"/>
</in-parameters>
<actions>
<!-- Critical financial closing logic -->
<script>ec.transaction.commitBeginOnly();</script>
<!-- ... closing operations ... -->
</actions>
</service>
Default Behavior (98.5% of framework services):
<!-- Most services - no transaction attribute needed -->
<service verb="create" noun="Example" authenticate="true">
<!-- Framework handles transactions automatically -->
</service>
Critical Operations (1.5% of framework services):
<!-- Financial/bulk operations requiring isolation -->
<service verb="close" noun="FinancialPeriod" authenticate="true"
transaction="force-new" transaction-timeout="600">
<!-- Long-running critical operation -->
</service>
| Scenario | Transaction Attribute | Timeout | When to Use |
|---|---|---|---|
| Standard CRUD | (none) | (none) | 98.5% of services |
| Financial Operations | force-new | 600-3600s | Period closing, payments |
| Bulk Data Operations | force-new | 600-1800s | Import/export, cleanup |
| Long-running Tasks | (none) | 600+ | Add timeout only if >5min |
| Record Locking Issues | ignore | (none) | Bulk operations only |
❌ Avoid transaction="cache"
❌ Avoid transaction="use-or-begin"
❌ Avoid transaction-timeout="60"
transaction="force-new" only for critical financial/bulk operationstransaction-timeout only for operations expected to run >5 minutesConsider:
{entityName}Id)Start with assets/entity_template.xml and customize:
python3 scripts/validate_entity.py path/to/your/entity.xml
Use references/field_types.md to choose appropriate types:
<!-- Primary Key -->
<field name="productId" type="id" is-pk="true"/>
<!-- Text Fields -->
<field name="productName" type="text-medium" enable-localization="true"/>
<field name="description" type="text-long" enable-localization="true"/>
<!-- Numeric Fields -->
<field name="price" type="currency-amount"/>
<field name="quantity" type="number-integer"/>
<!-- Date Fields -->
<field name="createdDate" type="date-time"/>
<field name="fromDate" type="date-time"/>
<field name="thruDate" type="date-time"/>
<!-- Status Fields -->
<field name="statusId" type="id" enable-audit-log="true"/>
<!-- Audit Fields -->
<field name="createdByUserAccountId" type="id" enable-audit-log="true"/>
<field name="lastUpdatedStamp" type="date-time"/>
<!-- In parent entity -->
<relationship type="many" related="com.example.OrderItem" short-alias="items">
<key-map field-name="orderId"/></relationship>
<!-- In child entity -->
<relationship type="one" related="com.example.OrderHeader" short-alias="order">
<key-map field-name="orderId"/></relationship>
<relationship type="one" related="moqui.basic.StatusItem" short-alias="status">
<key-map field-name="statusId"/></relationship>
<relationship type="one-nofk" title="Parent" related="com.example.Category" short-alias="parent">
<key-map field-name="parentCategoryId" related="categoryId"/></relationship>
<!-- Find single record -->
<entity-find-one entity-name="com.example.Product" value-field="product"/>
<!-- Find multiple records -->
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="statusId" value="PsActive"/>
<order-by field-name="productName"/>
</entity-find>
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="productTypeEnumId" from="productTypeEnumId" ignore-if-empty="true"/>
<econdition field-name="statusId" from="statusId" ignore-if-empty="true"/>
<econdition field-name="productName" operator="like" value="${searchText}%"/>
<order-by field-name="productName"/>
</entity-find>
<entity-find entity-name="com.example.Order" list="orderList">
<date-filter valid-date="orderDate"/>
<order-by field-name="orderDate" descending="true"/>
</entity-find>
<entity-find entity-name="com.example.Product" list="productList" count="totalCount">
<econdition field-name="statusId" value="PsActive"/>
<order-by field-name="productName"/>
</entity-find>
<!-- Manual pagination -->
<script>
def startIdx = pageIndex * pageSize
def endIdx = startIdx + pageSize
productList = productList.subList(startIdx, Math.min(endIdx, productList.size()))
</script>
<entity-find entity-name="com.example.Product" list="productList">
<econdition field-name="status.description" operator="like" value="${statusFilter}%"/>
<relationship-join relationship="status"/>
<order-by field-name="productName"/>
</entity-find>
<entity-find entity-name="com.example.ProductAndStatus" list="productList">
<econdition field-name="statusId" value="PsActive"/>
<econdition field-name="statusDescription" operator="like" value="${statusFilter}%"/>
<order-by field-name="productName"/>
</entity-find>
Choose appropriate authentication based on sensitivity:
anonymous-all - Public servicesanonymous-view - Read-only public datatrue - User must be logged inrequire-all-roles for restricted access<set field="hasPermission" from="ec.user.hasPermission('PRODUCT', 'CREATE')"/>
<if condition="!hasPermission">
<return error="true" message="Permission denied"/>
</if>
<if condition="!ec.user.isUserInRole('ADMIN')">
<script>
productList = productList.findAll { it.createdByUserAccountId == ec.user.userId }
</script>
</if>
# Validate single service file
python3 scripts/validate_service.py service/ExampleServices.xml
# Validate entire directory
python3 scripts/validate_service.py --directory service/
# Validate single entity file
python3 scripts/validate_entity.py entity/ExampleEntities.xml
# Validate entire directory
python3 scripts/validate_entity.py --directory entity/
# Format single file with backup
python3 scripts/format_moqui_xml.py service/ExampleServices.xml
# Format directory without backup
python3 scripts/format_moqui_xml.py --directory entity/ --no-backup
Executable tools for validation and generation:
Comprehensive documentation for reference while working:
Template files for starting new components:
Before using field names in code, verify against entity definitions:
# Find entity definition
find . -name "*.xml" -path "*/entity/*" | xargs grep -l "entity-name=\"YourEntity\""
# Check available fields
grep -A 5 -B 5 "<field name="fieldName"*/entity/YourEntity.xml
| Intended Field | Correct Field | Entity | Context |
|---|---|---|---|
| trackingUrl | masterTrackingUrl | ShipmentRouteSegment | Overall shipment tracking |
| trackingUrl | trackingUrl | ShipmentPackageRouteSeg | Package-level tracking |
| trackingIdNumber | masterTrackingCode | ShipmentRouteSegment | Overall tracking number |
| trackingIdNumber | trackingCode | ShipmentPackageRouteSeg | Package tracking number |
| externalId | otherPartyOrderId | OrderPart | External system reference |
Services can have misleading names - always verify actual behavior:
| Service | What It Actually Does | When to Use |
|---|---|---|
| ship#OrderPart | Creates NEW shipment + packs items + marks shipped | No existing shipment |
| create#OrderPartShipment | Creates shipment record only | Need custom packing logic |
| ship#Shipment | Marks existing shipment as shipped | Shipment exists, needs status change |
| update#OrderStatus | Changes order status only | Status transition needed |
<!-- WRONG: Assumes ship#OrderPart updates existing shipment -->
<service-call name="ship#OrderPart" in-map="[orderId: orderId]"/>
<!-- CORRECT: Different approaches for existing vs new -->
<if condition="existingShipment">
<service-call name="ship#Shipment" in-map="[shipmentId: shipmentId]"/>
<else>
<service-call name="ship#OrderPart" in-map="[orderId: orderId]"/>
</if>
Groovy closures have different variable scope rules than traditional blocks:
// BROKEN: logger not accessible in closure
existingShipments.each { shipment ->
logger.info("Shipment: ${shipment.shipmentId}") // NullPointerException!
}
// CORRECT: Use traditional for loop for outer variable access
for (EntityValue shipment in existingShipments) {
logger.info("Shipment: ${shipment.shipmentId}") // Works!
}
// CORRECT: Explicit variable passing
existingShipments.each { shipment ->
def log = logger // Explicit reference
log.info("Shipment: ${shipment.shipmentId}")
}
# 1. Fix syntax errors
groovy -cp . service/YourService.groovy
# 2. Test basic functionality
# Run service with minimal parameters
# 3. Add complex logic incrementally
# Test each addition separately
# 4. Full integration testing
# Test complete workflow
# Quick brace check
open_count=$(grep -o '{' service/YourService.groovy | wc -l)
close_count=$(grep -o '}' service/YourService.groovy | wc -l)
echo "Open: $open_count, Close: $close_count"
# Should be equal for valid syntax
Always ask these questions first:
Is there a Moqui service that does this?
Am I duplicating existing functionality?
Is this getting too complex?
// COMPLEX: Manual shipment creation (48 lines)
if (existingShipment) {
// Manual tracking updates
// Manual route segment creation
// Manual shipment status changes
} else {
// Manual shipment creation
// Manual package creation
// Manual status updates
}
// SIMPLE: Use Moqui services (15 lines)
if (existingShipment) {
ec.service.sync().name("ship#Shipment")(shipmentId: shipmentId)
} else {
ec.service.sync().name("ship#OrderPart")(orderId: orderId)
}
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience