MODEXPW-635 - Expose template tokens in EDIFACT email export#737
MODEXPW-635 - Expose template tokens in EDIFACT email export#737markusweigelt wants to merge 81 commits into
Conversation
…ar URL for Feign routing
mod-template-engine returns 400 when outputFormat does not match the format registered in the template. The claims template requires text/html.
Read outputFormat from template-engine response meta and forward it to EmailEntity so the email is sent in the same format the template was rendered in (e.g. text/html).
Wrap templateEngineClient and emailClient calls in EdifactException, derive attachment content type from fileFormat, extract buildAttachment and sendEmail helper methods, and add unit/integration tests for the tasklet and its decider.
Remove template context from TemplateProcessingRequest for now since required template variables are not yet known; mod-template-engine treats a missing context as an empty object. Add ORDERING + EMAIL test coverage.
Migrate EmailClient and TemplateEngineClient from @FeignClient to Spring 6 @HttpExchange. Update Spring Batch import paths to new package structure. Fix SendToEmailTaskletTest to use JobOperatorTestUtils and correct mock types.
…iguration Both clients use @HttpExchange and require explicit @bean registration via HttpServiceProxyFactory — they were missing, causing a NoSuchBeanDefinitionException when sendToEmailStep attempted to inject EmailClient.
Spring HttpServiceProxyFactory requires @RequestBody on POST parameters to resolve them as the request body — unlike Feign which inferred this from unannotated arguments.
…lization RestClient uses Jackson 3 (tools.jackson) which cannot deserialize into the abstract com.fasterxml.jackson.databind.JsonNode (Jackson 2). Return String instead and parse with ediObjectMapper in the tasklet.
…sponse DTO Jackson 3 (RestClient) cannot deserialize JSON into String or the abstract JsonNode (Jackson 2). A concrete DTO is the correct and consistent approach, matching how all other clients in this module handle responses.
Removed the erroneous CLAIMING integration type check; email delivery is driven exclusively by transmissionMethod == EMAIL regardless of integration type. Also added a comment in EdifactExportJobConfig summarising the decider conditions for all three optional steps.
Wrap organization, contributor-name-type, and identifier-type lookups in try/catch so a failed enrichment call no longer aborts the whole email export, and add unless="#result == null" on those plus tenantAddress so transient failures are not pinned in the cache.
Revert OrganizationsService and IdentifierTypeService to propagate lookup failures so the shared EDIFACT path keeps failing loud instead of NPEing, and degrade gracefully only in OrderEmailContextMapper via resolveOrganization/resolveIdentifierTypeName. Add tests for the throwing lookup paths.
Remove the order-email estimatedPrice cost token, which duplicated poLineEstimatedPrice. Resolve ediEmail once in SendToEmailTasklet with a clear "no email configuration" failure before use, so the template-id guard stays reachable and a missing config no longer NPEs.
Add an Order Email context-payload reference under Additional information: an annotated structure tree and an example JSON payload for the EDIFACT_ORDERS_EXPORT email transmission.
The cost context exposed a quantity equal to quantityPhysical plus quantityElectronic, which is not a real PO line field. Remove it from the DTO, mapper, tests, and README, and clarify that fundDistribution code is taken verbatim from the PO line without resolving fundId.
9d54d76 to
4c3fa7e
Compare
Drop the unused JsonNode import and ADDRESS constant, and route both cacheable methods through a private fetchTenantAddress helper so the @Cacheable getTenantAddress is no longer self-invoked via this.
Cover getUserContext: resolved name, username fallback when names are blank or personal is absent, and empty context on null response or client exception.
@SerhiiNosko I hope you're getting through the heat well. 🥵 Besides merging master, we also optimized the actual changes. |
Skip null source entries before mapping in the shared list helper to avoid NPEs. Also drop the obsolete estimatedPrice field from the documented order-line payload in README.
Replace the manual LogManager.getLogger() field with Lombok @log4j2 to match the other acquisitions services in this branch.
Instant.now().toString() emits a variable number of fractional digits depending on clock resolution, breaking the required millisecond format. Truncate to millis and add a regression test guarding the pattern.
Let ContributorNameTypeService return the raw cached result and handle lookup failures in OrderEmailContextMapper, so a failed type resolution degrades to an empty name without breaking the email context build.
|
@SerhiiNosko Thx for the review of this hugh PR and your approval! Can I merge this PR? There was still a question regarding caching. I would merge the Spring PR in the same step. |
by the way, locally did you check functionality with eureka-cli? |
Yes, we have a development system based on the Eureka CLI, and we have already tested the placeholder replacement there. We haven’t checked the permissions in the PR folio-org/mod-data-export-spring#380 yet - I’ll take care of that right away. But this should only be a formality. |
lets merge then, also for future you can create story to cover this functionality wit karate tests, karate tests are executed every night and do real testing of services without any mocks and always help us to find new bugs. In this case we will make sure that this newly implemented functionality always work as expected. |
|



MODEXPW-635
- Expose template tokens in EDIFACT email export
Purpose
Make order, order-line, and vendor-organization data available as
{{token}}placeholders in the email templates rendered for the EDIFACT email export. Before this change,SendToEmailTaskletinvokedTemplateEngineClient.processTemplatewithtemplateId+lang+outputFormatbut no context payload, so templates couldn't reference any order data — only the static template body reached the recipient.Approach
Ship the worker a structured context object that mod-template-engine substitutes against the configured template.
1. Context payload shape
2. Token surface exposed to templates
createdAtorganization.*:name,primaryAddress.{addressLine1, city, zipCode, country}order.*:poNumber,orderType,metadata.createdByUser.{id, firstName, lastName, fullName},shipTo.{id, address},billTo.{id, address}orderLine.*:poLineNumber,titleOrPackage,publisher,publicationDate,edition,rush,contributors[].{contributor, contributorNameType.{id, name}},details.productIds[].{productId, qualifier, productIdType.{id, name}},cost.{listUnitPrice, listUnitPriceElectronic, quantityPhysical, quantityElectronic, poLineEstimatedPrice, currency},fundDistribution[].code,vendorDetail.instructions3. Resolutions performed by context mapper
OrderEmailContextMapper.buildContext(orders)is the single entry point. It walks eachCompositePurchaseOrder, pairs eachPoLinewith its own parent order (so multi-order exports don't leak the first order'spoNumberonto later lines), and resolves the following FOLIO lookups before handoff:createdAt— generated at build time in ISO-8601 UTC.order.metadata.createdByUser— resolved frommetadata.createdByUserIdvia newUserService.getUserContext(...)into aUserContext{id, firstName, lastName, fullName};fullNameis"firstName lastName"trimmed, falling back tousername.order.shipTo/order.billTo— address UUID resolved to aTenantAddressContext{id, address}viaConfigurationService.getTenantAddress(...).orderLine.contributors[].contributorNameType.name— UUID → type name (e.g.Personal name) via cachedContributorNameTypeService.getContributorNameTypeName(uuid).orderLine.details.productIds[].productIdType.name— UUID → type name (e.g.ISBN) via cachedIdentifierTypeService.getIdentifierTypeName(uuid).orderLine.fundDistribution[]— exposes only the fundcode, taken as-is from the PO line (fundIdis not resolved;expenseClassId,distributionType, andvalueare not exposed).organization— resolved from the order vendor viaOrganizationsService; primary address picked with a strictisPrimary == truefilter."", quantities to0, lists to[], and unresolved sub-objects (Cost,VendorDetail,metadata, vendor, addresses) to empty contexts.Additional information
Example plain-text template body of mod-template-engine exercising every token:
Depends on
PR #732 - MODEXPW-625 - Enable Email Delivery for EDIFACT Export Jobs