MODEXPW-635 - Expose template tokens in EDIFACT email export#737
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.
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. |
|
Thanks for the note. Integration tests are of course even better. How does this work with this story - do you mean creating a Jira issue as a story type for this? Regarding the implementation of the integration tests, are you providing them based on the story, or are we supposed to deliver them? (We’re still fairly new here, hence these questions.)
I just tested the workflow via Spring again, and it works. The test was run in our environment; in general, we have already been doing this via Spring. Could you please give me the approval on the PR folio-org/mod-data-export-spring#380 in the Spring module? Then I would merge both PRs afterwards. |
Approved, so we can merge. |
|
Thanks for the approvals. 👍🏻
I’ll take a look at these integration tests and provide appropriate tests for our implementations. |




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