11import Foundation
22import Yosemite
33import UIKit
4+ import WooFoundation
45import protocol Storage. StorageManagerType
56
67/// View model for `AddEditCoupon` view
@@ -13,7 +14,7 @@ final class AddEditCouponViewModel: ObservableObject {
1314 ///
1415 private let editingOption : EditingOption
1516
16- private let onCompletion : ( ( Result < Coupon , Error > ) -> Void )
17+ private let onSuccess : ( Coupon ) -> Void
1718
1819 /// Defines the current notice that should be shown.
1920 /// Defaults to `nil`.
@@ -29,6 +30,18 @@ final class AddEditCouponViewModel: ObservableObject {
2930 }
3031 }
3132
33+ /// Defines the main action button text that should be shown.
34+ /// Switching between `Create` or `Save` action.
35+ ///
36+ var addEditCouponButtonText : String {
37+ switch editingOption {
38+ case . creation:
39+ return Localization . createButton
40+ case . editing:
41+ return Localization . saveButton
42+ }
43+ }
44+
3245 /// The value for populating the coupon discount type field based on the `discountType`.
3346 ///
3447 var discountTypeValue : TitleAndValueRow . Value {
@@ -127,7 +140,12 @@ final class AddEditCouponViewModel: ObservableObject {
127140 categoryIDs. isNotEmpty
128141 }
129142
143+ var shareCouponMessage : String {
144+ coupon? . generateShareMessage ( currencySettings: currencySettings) ?? " "
145+ }
146+
130147 var hasChangesMade : Bool {
148+ guard editingOption == . editing else { return true }
131149 let coupon = populatedCoupon
132150 return checkDiscountTypeUpdated ( for: coupon) ||
133151 checkAmountUpdated ( for: coupon) ||
@@ -142,6 +160,7 @@ final class AddEditCouponViewModel: ObservableObject {
142160 private( set) var coupon : Coupon ?
143161 private let stores : StoresManager
144162 private let storageManager : StorageManagerType
163+ private let currencySettings : CurrencySettings
145164 let timezone : TimeZone
146165
147166 /// When the view is updating or creating a new Coupon remotely.
@@ -162,22 +181,25 @@ final class AddEditCouponViewModel: ObservableObject {
162181 @Published var couponRestrictionsViewModel : CouponRestrictionsViewModel
163182 @Published var productOrVariationIDs : [ Int64 ]
164183 @Published var categoryIDs : [ Int64 ]
184+ @Published var showingCouponCreationSuccess : Bool = false
165185
166186 /// Init method for coupon creation
167187 ///
168188 init ( siteID: Int64 ,
169189 discountType: Coupon . DiscountType ,
170190 stores: StoresManager = ServiceLocator . stores,
171191 storageManager: StorageManagerType = ServiceLocator . storageManager,
192+ currencySettings: CurrencySettings = ServiceLocator . currencySettings,
172193 timezone: TimeZone = . siteTimezone,
173- onCompletion : @escaping ( ( Result < Coupon , Error > ) -> Void ) ) {
194+ onSuccess : @escaping ( Coupon ) -> Void ) {
174195 self . siteID = siteID
175196 editingOption = . creation
176197 self . discountType = discountType
177198 self . stores = stores
178199 self . storageManager = storageManager
200+ self . currencySettings = currencySettings
179201 self . timezone = timezone
180- self . onCompletion = onCompletion
202+ self . onSuccess = onSuccess
181203
182204 amountField = String ( )
183205 codeField = String ( )
@@ -187,23 +209,26 @@ final class AddEditCouponViewModel: ObservableObject {
187209 couponRestrictionsViewModel = CouponRestrictionsViewModel ( siteID: siteID)
188210 productOrVariationIDs = [ ]
189211 categoryIDs = [ ]
212+ generateRandomCouponCode ( )
190213 }
191214
192215 /// Init method for coupon editing
193216 ///
194217 init ( existingCoupon: Coupon ,
195218 stores: StoresManager = ServiceLocator . stores,
196219 storageManager: StorageManagerType = ServiceLocator . storageManager,
220+ currencySettings: CurrencySettings = ServiceLocator . currencySettings,
197221 timezone: TimeZone = . siteTimezone,
198- onCompletion : @escaping ( ( Result < Coupon , Error > ) -> Void ) ) {
222+ onSuccess : @escaping ( Coupon ) -> Void ) {
199223 siteID = existingCoupon. siteID
200224 coupon = existingCoupon
201225 editingOption = . editing
202226 discountType = existingCoupon. discountType
203227 self . stores = stores
204228 self . storageManager = storageManager
229+ self . currencySettings = currencySettings
205230 self . timezone = timezone
206- self . onCompletion = onCompletion
231+ self . onSuccess = onSuccess
207232
208233 // Populate fields
209234 amountField = existingCoupon. amount
@@ -233,13 +258,50 @@ final class AddEditCouponViewModel: ObservableObject {
233258 codeField = code
234259 }
235260
236- func updateCoupon( coupon: Coupon , onUpdateFinished: @escaping ( ) -> Void ) {
261+ func completeCouponAddEdit( coupon: Coupon , onUpdateFinished: @escaping ( ) -> Void ) {
262+ switch editingOption {
263+ case . creation:
264+ createCoupon ( coupon: coupon)
265+ case . editing:
266+ updateCoupon ( coupon: coupon, onUpdateFinished: onUpdateFinished)
267+ }
268+ }
269+
270+ private func createCoupon( coupon: Coupon ) {
271+ trackCouponCreateInitiated ( with: coupon)
272+
273+ if let validationError = validateCouponLocally ( coupon) {
274+ notice = NoticeFactory . createCouponErrorNotice ( validationError,
275+ editingOption: editingOption)
276+ return
277+ }
278+
279+ isLoading = true
280+ let action = CouponAction . createCoupon ( coupon, siteTimezone: timezone) { [ weak self] result in
281+ guard let self = self else { return }
282+ self . isLoading = false
283+ switch result {
284+ case . success( let coupon) :
285+ ServiceLocator . analytics. track ( . couponCreationSuccess)
286+ self . coupon = coupon
287+ self . onSuccess ( coupon)
288+ self . showingCouponCreationSuccess = true
289+ case . failure( let error) :
290+ DDLogError ( " ⛔️ Error creating the coupon: \( error) " )
291+ ServiceLocator . analytics. track ( . couponCreationFailed, withError: error)
292+ self . notice = NoticeFactory . createCouponErrorNotice ( . other( error: error) ,
293+ editingOption: self . editingOption)
294+ }
295+ }
296+ stores. dispatch ( action)
297+ }
298+
299+ private func updateCoupon( coupon: Coupon , onUpdateFinished: @escaping ( ) -> Void ) {
237300 trackCouponUpdateInitiated ( with: coupon)
238301
239302 if let validationError = validateCouponLocally ( coupon) {
240303 notice = NoticeFactory . createCouponErrorNotice ( validationError,
241304 editingOption: editingOption)
242- onCompletion ( . failure( validationError) )
243305 return
244306 }
245307
@@ -248,9 +310,9 @@ final class AddEditCouponViewModel: ObservableObject {
248310 guard let self = self else { return }
249311 self . isLoading = false
250312 switch result {
251- case . success( _ ) :
313+ case . success( let updatedCoupon ) :
252314 ServiceLocator . analytics. track ( . couponUpdateSuccess)
253- self . onCompletion ( result )
315+ self . onSuccess ( updatedCoupon )
254316 onUpdateFinished ( )
255317 case . failure( let error) :
256318 DDLogError ( " ⛔️ Error updating the coupon: \( error) " )
@@ -329,6 +391,19 @@ private extension AddEditCouponViewModel {
329391 return coupon. discountType != initialCoupon. discountType
330392 }
331393
394+ func checkUsageRestrictionsOnCreation( of coupon: Coupon ) -> Bool {
395+ coupon. maximumAmount. isNotEmpty ||
396+ coupon. minimumAmount. isNotEmpty ||
397+ ( coupon. usageLimit ?? 0 ) > 0 ||
398+ ( coupon. usageLimitPerUser ?? 0 ) > 0 ||
399+ ( coupon. limitUsageToXItems ?? 0 ) > 0 ||
400+ coupon. emailRestrictions. isNotEmpty ||
401+ coupon. individualUse ||
402+ coupon. excludeSaleItems ||
403+ coupon. excludedProductIds. isNotEmpty ||
404+ coupon. excludedProductCategories. isNotEmpty
405+ }
406+
332407 func checkUsageRestrictionsUpdated( for coupon: Coupon ) -> Bool {
333408 guard let initialCoupon = self . coupon else {
334409 return false
@@ -396,6 +471,17 @@ private extension AddEditCouponViewModel {
396471 return coupon. freeShipping != initialCoupon. freeShipping
397472 }
398473
474+ func trackCouponCreateInitiated( with coupon: Coupon ) {
475+ ServiceLocator . analytics. track ( . couponCreationInitiated, withProperties: [
476+ " discount_type " : coupon. discountType. rawValue,
477+ " has_expiry_date " : coupon. dateExpires != nil ,
478+ " includes_free_shipping " : coupon. freeShipping,
479+ " has_description " : coupon. description. isNotEmpty,
480+ " has_product_or_category_restrictions " : coupon. excludedProductCategories. isNotEmpty || coupon. excludedProductIds. isNotEmpty,
481+ " has_usage_restrictions " : checkUsageRestrictionsOnCreation ( of: coupon)
482+ ] )
483+ }
484+
399485 func trackCouponUpdateInitiated( with coupon: Coupon ) {
400486 ServiceLocator . analytics. track ( . couponUpdateInitiated, withProperties: [
401487 " discount_type_updated " : checkDiscountTypeUpdated ( for: coupon) ,
@@ -474,5 +560,7 @@ private extension AddEditCouponViewModel {
474560 " Reads like: Edit Categories " )
475561 static let createCouponTitle = NSLocalizedString ( " Create coupon " , comment: " Title of the Create coupon screen " )
476562 static let editCouponTitle = NSLocalizedString ( " Edit coupon " , comment: " Title of the Edit coupon screen " )
563+ static let saveButton = NSLocalizedString ( " Save " , comment: " Action for saving a Coupon remotely " )
564+ static let createButton = NSLocalizedString ( " Create " , comment: " Action for creating a Coupon remotely " )
477565 }
478566}
0 commit comments