1-- ATL Transformation: Legacy Customer to CustomerManagement v2.0
2-- Migrates legacy customer models to improved metamodel structure
3-- Part of the Model Refactoring Pipeline tutorial
4-- Uses Australian English spelling conventions
5-- @path Legacy=/Legacy/LegacyCustomer.ecore
6-- @path Customer=/Customer/CustomerManagement.ecore
7
8module LegacyCustomer2CustomerManagement;
9create OUT: Customer from IN: Legacy;
10
11-- ===========================================================================
12-- Global Variables for deduplication
13-- ===========================================================================
14
15-- Track created organisations by ABN to avoid duplicates
16helper def: organisationsByABN: Map(String, Customer!Organisation) = Map{};
17
18-- Track created contacts by email to avoid duplicates
19helper def: contactsByEmail: Map(String, Customer!Contact) = Map{};
20
21-- Migration timestamp for audit fields
22helper def: migrationTimestamp: OclAny = OclAny.now();
23
24-- ===========================================================================
25-- Helper: Normalise organisation type string to enum
26-- ===========================================================================
27helper def: normaliseOrganisationType(typeStr: String): Customer!OrganisationType =
28 let normalised: String = typeStr.trim().toUpper() in
29 if normalised = 'COMPANY' or normalised = 'PTY LTD' or normalised = 'PRIVATE' then
30 #PRIVATE_COMPANY
31 else if normalised = 'PUBLIC' or normalised = 'LIMITED' then
32 #PUBLIC_COMPANY
33 else if normalised = 'GOVT' or normalised = 'GOVERNMENT' then
34 #GOVERNMENT
35 else if normalised = 'NFP' or normalised = 'NON-PROFIT' or normalised = 'NONPROFIT' then
36 #NON_PROFIT
37 else if normalised = 'PARTNERSHIP' then
38 #PARTNERSHIP
39 else if normalised = 'INDIVIDUAL' or normalised = 'SOLE TRADER' then
40 #SOLE_TRADER
41 else
42 #PRIVATE_COMPANY
43 endif endif endif endif endif endif;
44
45-- ===========================================================================
46-- Helper: Normalise customer status string to enum
47-- ===========================================================================
48helper def: normaliseCustomerStatus(statusStr: String): Customer!CustomerStatus =
49 let normalised: String = statusStr.trim().toUpper() in
50 if normalised = 'NEW' or normalised = 'PROSPECT' or normalised = 'LEAD' then
51 #PROSPECT
52 else if normalised = 'ACTIVE' or normalised = 'CURRENT' then
53 #ACTIVE
54 else if normalised = 'INACTIVE' or normalised = 'DORMANT' then
55 #INACTIVE
56 else if normalised = 'SUSPENDED' or normalised = 'HOLD' or normalised = 'ON HOLD' then
57 #SUSPENDED
58 else if normalised = 'CLOSED' or normalised = 'DELETED' or normalised = 'ARCHIVED' then
59 #CLOSED
60 else
61 #PROSPECT
62 endif endif endif endif endif;
63
64-- ===========================================================================
65-- Helper: Normalise Australian state abbreviations
66-- ===========================================================================
67helper def: normaliseState(stateStr: String): String =
68 let normalised: String = stateStr.trim().toUpper() in
69 if normalised = 'NEW SOUTH WALES' then 'NSW'
70 else if normalised = 'VICTORIA' then 'VIC'
71 else if normalised = 'QUEENSLAND' then 'QLD'
72 else if normalised = 'SOUTH AUSTRALIA' then 'SA'
73 else if normalised = 'WESTERN AUSTRALIA' then 'WA'
74 else if normalised = 'TASMANIA' then 'TAS'
75 else if normalised = 'NORTHERN TERRITORY' then 'NT'
76 else if normalised = 'AUSTRALIAN CAPITAL TERRITORY' then 'ACT'
77 else normalised
78 endif endif endif endif endif endif endif endif;
79
80-- ===========================================================================
81-- Helper: Normalise postcode to 4 digits
82-- ===========================================================================
83helper def: normalisePostcode(postcodeStr: String): String =
84 let trimmed: String = postcodeStr.trim() in
85 if trimmed.size() < 4 then
86 '0'.repeat(4 - trimmed.size()) + trimmed
87 else
88 trimmed
89 endif;
90
91-- ===========================================================================
92-- Helper: Get unique ABNs from legacy customers
93-- ===========================================================================
94helper def: uniqueABNs: Set(String) =
95 Legacy!Customer.allInstances()
96 ->select(c | not c.abn.oclIsUndefined() and c.abn.size() > 0)
97 ->collect(c | c.abn.trim())
98 ->asSet();
99
100-- ===========================================================================
101-- Helper: Get all legacy customers for a given ABN
102-- ===========================================================================
103helper def: customersForABN(abn: String): Sequence(Legacy!Customer) =
104 Legacy!Customer.allInstances()
105 ->select(c | c.abn.trim() = abn);
106
107-- ===========================================================================
108-- Helper: Generate identifier for organisation
109-- ===========================================================================
110helper def: generateOrgIdentifier(abn: String, name: String): String =
111 if abn.size() > 0 then
112 'ORG-' + abn.replace(' ', '')
113 else
114 'ORG-' + name.toUpper().substring(1, 3.min(name.size())) + '-' +
115 OclAny.newGuid().substring(1, 8)
116 endif;
117
118-- ===========================================================================
119-- Rule: CustomerDatabase -> CustomerManagementSystem
120-- Root transformation with all contained elements
121-- ===========================================================================
122rule CustomerDatabase2System {
123 from
124 db: Legacy!CustomerDatabase
125 to
126 sys: Customer!CustomerManagementSystem (
127 name <- db.name,
128 description <- 'Migrated from legacy system on ' + thisModule.migrationTimestamp.toString(),
129 organisations <- thisModule.uniqueABNs
130 ->collect(abn | thisModule.createOrganisation(
131 thisModule.customersForABN(abn)->first()
132 )),
133 customers <- db.customers,
134 categories <- thisModule.createDefaultCategories()
135 )
136}
137
138-- ===========================================================================
139-- Lazy Rule: Create Organisation from legacy Customer data
140-- Deduplicates by ABN (Australian Business Number)
141-- ===========================================================================
142lazy rule createOrganisation {
143 from
144 c: Legacy!Customer
145 to
146 org: Customer!Organisation (
147 identifier <- thisModule.generateOrgIdentifier(c.abn, c.companyName),
148 name <- c.companyName,
149 description <- 'Organisation for ' + c.companyName,
150 organisationType <- thisModule.normaliseOrganisationType(c.companyType),
151 tradingName <- if c.tradingName.oclIsUndefined() then c.companyName
152 else c.tradingName endif,
153 australianBusinessNumber <- c.abn.replace(' ', ''),
154 australianCompanyNumber <- if c.acn.oclIsUndefined() then ''
155 else c.acn.replace(' ', '') endif,
156 createdAt <- thisModule.migrationTimestamp,
157 modifiedAt <- thisModule.migrationTimestamp,
158 addresses <- Sequence{tradingAddr},
159 contacts <- thisModule.customersForABN(c.abn)
160 ->collect(cust | thisModule.createContact(cust))
161 ),
162 tradingAddr: Customer!Address (
163 addressType <- #TRADING,
164 streetAddress <- c.streetAddress,
165 suburb <- c.city,
166 state <- thisModule.normaliseState(c.state),
167 postcode <- thisModule.normalisePostcode(c.postcode),
168 country <- if c.country.oclIsUndefined() or c.country.size() = 0
169 then 'Australia'
170 else c.country endif
171 )
172}
173
174-- ===========================================================================
175-- Lazy Rule: Create Contact from legacy Customer data
176-- ===========================================================================
177lazy rule createContact {
178 from
179 c: Legacy!Customer
180 to
181 contact: Customer!Contact (
182 name <- c.contactName,
183 description <- 'Contact for ' + c.name,
184 contactType <- #PRIMARY,
185 email <- c.contactEmail,
186 phone <- c.contactPhone,
187 mobile <- if c.contactMobile.oclIsUndefined() then '' else c.contactMobile endif,
188 position <- if c.contactPosition.oclIsUndefined() then '' else c.contactPosition endif
189 )
190}
191
192-- ===========================================================================
193-- Rule: Customer -> Customer
194-- Maps legacy customer to new structure with proper references
195-- ===========================================================================
196rule Customer2Customer {
197 from
198 c: Legacy!Customer
199 to
200 cust: Customer!Customer (
201 identifier <- c.id,
202 name <- c.name,
203 description <- 'Customer account',
204 status <- thisModule.normaliseCustomerStatus(c.status),
205 creditLimit <- c.creditLimit,
206 paymentTermsDays <- if c.paymentTerms.oclIsUndefined() then 30
207 else c.paymentTerms endif,
208 createdAt <- thisModule.migrationTimestamp,
209 modifiedAt <- thisModule.migrationTimestamp,
210 organisation <- thisModule.createOrganisation(
211 Legacy!Customer.allInstances()->any(cust |
212 cust.abn.trim() = c.abn.trim()
213 )
214 ),
215 primaryContact <- thisModule.createContact(c),
216 billingAddress <- billingAddr,
217 deliveryAddress <- deliveryAddr,
218 notes <- if c.notes.oclIsUndefined() or c.notes.size() = 0
219 then Sequence{}
220 else Sequence{custNote}
221 endif
222 ),
223 billingAddr: Customer!Address (
224 addressType <- #BILLING,
225 streetAddress <- c.streetAddress,
226 suburb <- c.city,
227 state <- thisModule.normaliseState(c.state),
228 postcode <- thisModule.normalisePostcode(c.postcode),
229 country <- if c.country.oclIsUndefined() or c.country.size() = 0
230 then 'Australia'
231 else c.country endif
232 ),
233 deliveryAddr: Customer!Address (
234 addressType <- #DELIVERY,
235 streetAddress <- c.streetAddress,
236 suburb <- c.city,
237 state <- thisModule.normaliseState(c.state),
238 postcode <- thisModule.normalisePostcode(c.postcode),
239 country <- if c.country.oclIsUndefined() or c.country.size() = 0
240 then 'Australia'
241 else c.country endif
242 ),
243 custNote: Customer!CustomerNote (
244 content <- c.notes,
245 createdAt <- thisModule.migrationTimestamp,
246 createdBy <- 'Migration'
247 )
248}
249
250-- ===========================================================================
251-- Called Rule: Create default customer categories
252-- ===========================================================================
253rule createDefaultCategories() {
254 to
255 standard: Customer!CustomerCategory (
256 name <- 'Standard',
257 code <- 'STD',
258 description <- 'Standard customer category',
259 discountPercentage <- 0
260 ),
261 premium: Customer!CustomerCategory (
262 name <- 'Premium',
263 code <- 'PRM',
264 description <- 'Premium customer with enhanced discounts',
265 discountPercentage <- 10
266 ),
267 enterprise: Customer!CustomerCategory (
268 name <- 'Enterprise',
269 code <- 'ENT',
270 description <- 'Enterprise customer with volume discounts',
271 discountPercentage <- 15
272 )
273 do {
274 Sequence{standard, premium, enterprise};
275 }
276}