Untitled

 avatar
unknown
plain_text
2 years ago
58 kB
6
Indexable
/*
    01/22/21 CB@IC Initial Creation(00189679)

    Test class: OrderAndReturnCtrlTest
*/

public class OrderAndReturnCtrl {


    public OrderAndReturnCtrl(){}
    
    
    public static final String PO_BOX_EXPR = '(?i)[\\s\\S]*p[\\.\\s]{0,2}(o[\\.\\s]{0,2})? ?b((ox)|o|x)[\\s\\S]*'; //String contains some variant of P.O. Box
    public static final Id REFUND_REQUEST_PRODUCT_ID = ApexCodeSettings__c.getOrgDefaults().Refund_Request_Id__c;
    
    
    @AuraEnabled
    public static string init(String caseId){
        system.debug('caseId-->'+caseId);
        JSONGenerator gen = JSON.createGenerator(true);

        gen.writeStartObject();
            gen.writeObjectField('order', createOrderWrapper(caseId));
            gen.writeObjectField('recordTypes', getRecordTypes());            
            //gen.writeObjectField('countries', getCountries());
            gen.writeObjectField('countries', getCountry());
            gen.writeObjectField('states', getStates());
        gen.close();

        return gen.getAsString();
    }   

    @AuraEnabled(cacheable=true) 
    public static string searchProducts(String searchTerm, String[] productFilters, String returnType){
        System.debug('searchTerm: ' + searchTerm);
        System.debug('productFilters: ' + productFilters);
        System.debug('returnType: ' + returnType);
        if(!String.isNotEmpty(searchTerm)) {
            System.debug('returning');
            return '';
        }

        String term = searchTerm.replace('*', '%') + '%';
        Set<Id> partsAlreadyListed = new Set<Id>();      

        String query = 'SELECT Orderable__c, Case_Creation__c, IsActive, Name, Description, Product_Image__c, RecordType.Name, RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Stockroom__c, Physical_Stock__c, Has_Replacement__c, Recall_Count__c, Order_Override__c, Canada_Stock_Available__c, Part_Type__c,Preferred_Return_Carrier__c, Product_Region__c ' +
        'FROM Product2 ' +
        'WHERE (Orderable__c = true OR Case_Creation__c = true) AND Name LIKE :term AND RecordType.DeveloperName = \'Amer_Products\' AND Stockroom__c != \'None\' AND Family = \'First Alert\'' +  ' AND Id != :REFUND_REQUEST_PRODUCT_ID ';//FABase Migration : Updated record type from product to Amer_Products
        
        if(productFilters != null && !productFilters.isEmpty()){
            query += 'AND FAMILY IN :productFilters ';
        }

        query += 'ORDER BY Name LIMIT 100';
        system.debug('query: ' + query);
        //Matching Products
        Product2[] prods = Database.query(query);

        system.debug('prods: ' + JSON.serialize(prods));

        Set<Id> addedIds = new Map<Id, SObject>(prods).keySet().clone();
        system.debug('addedIds: ' + JSON.serialize(addedIds));
        Replacement__c[] replacements = [
            SELECT Replacement_Product__r.Orderable__c, Replacement_Product__r.IsActive, Replacement_Product__r.Stockroom__c, Replacement_Product__r.Physical_Stock__c, Replacement_Product__r.Name, Replacement_Product__r.Description, Replacement_Product__r.Product_Image__c, Replacement_Product__r.RecordType.Name, Replacement_Product__r.RecordType.DeveloperName, Replacement_Product__r.Family, Replacement_Product__r.Available_Balance__c, Replacement_Product__r.In_Transit__c, Replacement_Product__r.On_Order__c, Replacement_Product__r.Recall_Count__c, Replacement_Product__r.Order_Override__c, Replacement_Product__r.Has_Replacement__c, Replacement_Product__r.Canada_Stock_Available__c, Replacement_Product__r.Part_Type__c, Replacement_Product__r.Preferred_Return_Carrier__c, Replacement_Product__r.Product_Region__c
            FROM Replacement__c
            WHERE Replacement_Product__r.Orderable__c = TRUE AND Obsolete_Product__c = :prods AND Replacement_Product__c != :prods
            ORDER BY Replacement_Product__r.Name ASC
        ];
  system.debug('replacements: ' + JSON.serialize(replacements));
        for(Replacement__c replacement : replacements) {
            if(!addedIds.contains(replacement.Replacement_Product__c)) {
                System.debug('66');
                addedIds.add(replacement.Replacement_Product__c);
                prods.add(replacement.Replacement_Product__r);
            } else {
                System.debug('70');
            }
        }

        //Bill - Need to show active replacements for inactive products. -> 
        //Monica - Updated to abide by Orderable__c and/or Case_Creation__c checkbox instead of IsActive and using below
        /*
        for(Integer i = 0; i < prods.size(); i++)
            if(prods[i].Orderable__c != true)
                prods.remove(i);
        */

        //All parts for matching products
        //TODO: fix to use order wrapper once we pass it
        Set<Id> prodIds = (new Map<Id, SObject>(prods)).keySet();
        Map<Id, Part[]> productParts = getParts(prodIds, false);

        List<Part> parts = new List<Part>();

        system.debug('productParts: ' + JSON.serialize(productParts));
        system.debug('prods: ' + JSON.serialize(prods));
        for(Product2 prod : prods) {
            //MG added orderable condition
            if(prod.Orderable__c) {
                parts.add(new Part(prod, false));
                for(Part p : productParts.get(prod.Id)) {
                    parts.add(p);
                    partsAlreadyListed.add(p.p.Id);
                }
            }
        }

        List<Part> returnParts = new List<Part>();

        for(Product2 prod : prods) {
            if(prod.RecordType.DeveloperName == 'Amer_Products') {
              returnParts.add(new Part(prod, false));
                for(Part rp : productParts.get(prod.Id)) {
                    if(rp.p.RecordType.DeveloperName == 'Amer_Products') {
                        returnParts.add(rp);
                        partsAlreadyListed.add(rp.p.Id);    
                    }
                }  
            }
        }

        for(Product2 p : Database.query(
                'SELECT Name, Description, Product_Image__c, RecordType.Name, RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Stockroom__c, Physical_Stock__c, Has_Replacement__c, Recall_Count__c, Order_Override__c, Canada_Stock_Available__c, Part_Type__c, Preferred_Return_Carrier__c, Orderable__c, Product_Region__c ' +
                'FROM Product2 ' +
                'WHERE (Orderable__c = true OR Case_Creation__c = true) AND Name LIKE :term AND RecordType.DeveloperName = \'Part\' AND Stockroom__c != \'None\' AND Id != :partsAlreadyListed ')) {
            //if(step == 1 && p.Orderable__c == true) {
            
                    
            parts.add(new Part(p, false));
            if(p.RecordType.DeveloperName == 'Part') {
                returnParts.add(new Part(p, false));
            }
        }
        loadPriceData(parts);
system.debug('parts: ' + JSON.serialize(parts));
system.debug('returnParts: ' + JSON.serialize(returnParts));
        if((parts == null || parts.isEmpty()) && returnType.equals('Disposal')){
            parts = searchDisposalParts(term);

        }
system.debug('parts: ' + JSON.serialize(parts));
        JSONGenerator gen = JSON.createGenerator(true);

        gen.writeStartObject();
            gen.writeObjectField('parts', parts);
        gen.close();
        return gen.getAsString();
    }

    @AuraEnabled
    public static string getDisposalParts(){
        JSONGenerator gen = JSON.createGenerator(true);

        gen.writeStartObject();
            gen.writeObjectField('parts', loadDisposalParts());
        gen.close();


        return gen.getAsString();
    }

    @AuraEnabled
    public static string populateCityState(String postalCode){
        String ShippingCity;
        String ShippingStateCode;
        String ShippingCountryCode;

        System.debug('postalCode: ' + postalCode);

        try {
            Map<String, String> cityStateInfo = lookupCityState(postalCode, false);

            System.debug('cityStateInfo: ' + cityStateInfo);



            if(!String.isBlank(cityStateInfo.get('city')))
               ShippingCity = cityStateInfo.get('city');
            if(!String.isBlank(cityStateInfo.get('state')))
                ShippingStateCode = cityStateInfo.get('state').trim().toUpperCase();
            if(postalCode != null && Pattern.matches('\\d{5}', postalCode) && (!String.isBlank(cityStateInfo.get('city')) || !String.isBlank(cityStateInfo.get('state'))))
                ShippingCountryCode = 'US';
            if(postalCode != null && Pattern.matches('^[ABCEGHJKLMNPRSTVXY]\\d[A-Z] ?\\d[A-Z]\\d$', postalCode.toUpperCase()) && (!String.isBlank(cityStateInfo.get('city')) || !String.isBlank(cityStateInfo.get('state'))))
                ShippingCountryCode = 'CA';
        } catch (Exception e) {
            throw new AuraHandledException(e.getMessage());
        }

        JSONGenerator gen = JSON.createGenerator(true);

        gen.writeStartObject();
            gen.writeObjectField('city', ShippingCity);
            gen.writeObjectField('state', ShippingStateCode);
            gen.writeObjectField('country', ShippingCountryCode);
        gen.close();

        return gen.getAsString();
    }


    @AuraEnabled
    public static String completeOrder(String orderWrapper)
    {
        System.debug('orderWrapper--> '+orderWrapper);
        OrderWrapper ow = (OrderWrapper)JSON.deserialize(orderWrapper, OrderWrapper.class);

        savePoint sp = Database.setSavePoint();
        System.debug('ow--> '+JSON.serialize(ow));
        System.debug('ow.UpdateShippingAddress: ' + ow.UpdateShippingAddress);
        
        //FA Migration- Commnted the below code and updating the Contact fields after completing the order
        /*if(ow.UpdateShippingAddress){
            Account acct = new Account (
                Id = ow.AccountId,
                ShippingStreet = ow.ShippingStreet,
                ShippingCity = ow.ShippingCity,
                ShippingState = ow.ShippingState ,
                ShippingPostalCode = ow.ShippingPostalCode,
                ShippingCountry = ow.ShippingCountry,
                Email__c  = ow.Email
            );

            Database.DMLOptions dml = new Database.DMLOptions(); 
            dml.DuplicateRuleHeader.allowSave = true;
            dml.DuplicateRuleHeader.runAsCurrentUser = true;

            Database.SaveResult sr = Database.update(acct, dml);

            Contact c = new Contact( 
                Id = ow.AccountContactId, 
                Phone = ow.Phone
            );
            
            sr = Database.update(c, dml);
        }*/
        if(ow.UpdateShippingAddress){
            Contact con = new Contact (
                Id = ow.AccountContactId,
                MailingStreet = ow.ShippingStreet,
                MailingCity = ow.ShippingCity,
                MailingState = ow.ShippingState,
                MailingPostalCode = ow.ShippingPostalCode,
                MailingCountry = ow.ShippingCountry,
                Email = ow.Email,
                Phone = ow.Phone
            );
        
            Database.DMLOptions dml = new Database.DMLOptions(); 
            dml.DuplicateRuleHeader.allowSave = true;
            dml.DuplicateRuleHeader.runAsCurrentUser = true;
        
            Database.SaveResult sr = Database.update(con, dml);
        }
        

        // logic to update mailing address?
        Boolean hasReturn = ow.ReturnLineItems.size() > 0 && ow.ReturnNeeded == 'true';
        Boolean advancedExchange = ow.ReturnType == 'Advanced Exchange' && ow.ReturnNeeded == 'true';
        Boolean hasOrderParts = !ow.OrderLineItems.isEmpty() || ow.Type == 'Refund';

        
        String message = '';
        String orderId = '';
        String returnId = '';

        try{
            Order o = new Order();

            if(hasReturn){
                system.debug('has return');
                if(hasOrderParts){
                    system.debug('has order parts');
                    o = createOrder(ow);

                    if(o.Id != null){
                        System.debug('order created, trying return');
                        orderId = o.Id;
                        message = 'ok';
                        createReturn(o, ow);
                    } else {
                        message = 'not ok';
                    }
                } else {
                    system.debug('no order parts');
                    String woId = createReturn(o, ow);

                    message = 'ok';
                    orderId = woId;
                }
            } else {
                system.debug('no return');
                o = createOrder(ow);
                if(o.Id != null){
                    message = 'ok';
                    orderId = o.Id;
                } else {
                    message = 'not ok';
                }
            }

            if(ow.SendPaymentRequest){
                createChargentRequest(o);
            }

        } catch(DmlException ex){
            Database.rollback(sp);
            System.debug('there was an error');
            System.debug('message: ' + ex.getMessage());
            message = 'not okay';
        }

        JSONGenerator gen = JSON.createGenerator(true);

        gen.writeStartObject();
            gen.writeObjectField('status', message);
            gen.writeObjectField('orderId', orderId);
        gen.close();

        return gen.getAsString();
    }

    private static Boolean createChargentRequest(Order o){

        try{

            ChargentBase__Gateway__c gateway = [SELECT Id FROM ChargentBase__Gateway__c WHERE ChargentBase__Active__c = true LIMIT 1];
            Account personContact = [SELECT Id FROM Account WHERE Id =:o.AccountId]; //FABase Migration: Removed 'PersonContactId' from Query

            ChargentOrders__ChargentOrder__c chargentOrder = new ChargentOrders__ChargentOrder__c();

            Order ord = [SELECT Id, TotalAmount FROM Order WHERE Id =:o.Id LIMIT 1];

            system.debug('total amount: ' + o.TotalAmount);


            chargentOrder.Order__c = o.Id;
            chargentOrder.ChargentOrders__Account__c = o.AccountId;
            chargentOrder.ChargentOrders__Gateway__c = gateway.Id;
            chargentOrder.ChargentOrders__Subtotal__c = ord.TotalAmount;
            system.debug('inserting charget order');

            insert chargentOrder;

            system.debug('chargent order inserted');
                    
            ChargentOrders__Payment_Request__c paymentRequest = new ChargentOrders__Payment_Request__c();
            paymentRequest.ChargentOrders__ChargentOrder__c = chargentOrder.Id;
            paymentRequest.Order__c = o.Id;
            //FABase Migration commented the below line
            paymentRequest.ChargentOrders__Billing_Contact__c = o.ShipToContactId; 
            paymentRequest.ChargentOrders__Send_Payment_Request_Email__c = true;

            system.debug('insert payment request');
            insert paymentRequest;
            system.debug('payment request inserted');

            return true;
        } catch (Exception ex){
            system.debug('there was an issue creating charget orders');
            system.debug(ex.getMessage());

            return false;
        }
    }

    private static Order createOrder(OrderWrapper ow){
        Order ord = new Order(
            AccountId = ow.AccountId,
            ShipToContactId = ow.AccountContactId,
            EffectiveDate = Date.Today(),
            Case__c = ow.Id,
            PriceBook2Id = ow.PriceBookId,
            ShippingStreet = ow.ShippingStreet,
            ShippingCity = ow.ShippingCity,
            ShippingState  = ow.ShippingState ,
            ShippingPostalCode = ow.ShippingPostalCode,
            ShippingCountry = ow.ShippingCountry,
            Type = ow.Type,
            Status = 'Draft',
            RMA_Type__c = 'None',
            Billing_Email__c = ow.Email,
            Waive_Shipping_Fee__c = ow.WaiveShipping
        );


        if(ord.ShippingCountry != 'CA' && ord.ShippingCountry != 'MX' && ord.ShippingCountry != 'US') {
            ord.Expedited_Shipping__c = 'FedEx International'; 
        } else if(IsPOBox(ord.ShippingStreet)) {
           ord.Expedited_Shipping__c = 'USPS'; 
        } else if(ow.ExpediteShipping) {
            ord.Expedited_Shipping__c = 'Expedited';
        } else if(ord.ShippingCountry == 'CA'){
            ord.Expedited_Shipping__c = 'Standard CA';
        } else {
            Boolean containsKey = false;
            for(Part p : ow.OrderLineItems) {
                if(p.p.Part_Type__c == 'Key') {
                    containsKey = true;
                    break;
                }
            }
            if(containsKey) {
                ord.Expedited_Shipping__c = 'USPS';
            }
            else {
                ord.Expedited_Shipping__c = 'Standard'; 
            }
        }

        ord.International__c = ord.ShippingCountry != 'US';

        if(ow.ReturnNeeded != 'true') {
            ord.RMA_Type__c = 'None';
        } else {
            ord.RMA_Type__c = ow.ReturnType;
        }

        if(ow.ReturnType == 'Advanced Exchange' && ow.ReturnNeeded == 'true'){
            ord.Advanced_Exchange_Accepted__c = false;
            ord.Advanced_Exchange_Date__c = null;
        }

        try{
            insert ord;

            Boolean itemsCreated = createOrderItems(ord, ow);
            if(itemsCreated){
                ord.Status = 'Activated';
                update ord;
            } else {
                System.debug('could not create order items)');
            }
        } catch(DmlException ex){
            System.debug('there was a problem: ' + ex.getMessage());
        }

        return ord;
    }

    private static Boolean createOrderItems(Order ord, OrderWrapper ow) {
        try {
            List<Part> partsToProcess = ow.OrderLineItems;
            if(ord.Type == 'Refund') {
                List<Product2> p = [SELECT IsActive, Orderable__c, Name, Description, Product_Image__c, RecordType.Name, RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Stockroom__c, Physical_Stock__c, Has_Replacement__c, Recall_Count__c, Order_Override__c, Canada_Stock_Available__c, Part_Type__c,Preferred_Return_Carrier__c, Product_Region__c FROM Product2 WHERE Id = :REFUND_REQUEST_PRODUCT_ID LIMIT 1];
                if(!p.isEmpty()) {
                    Part aPart = new Part(p[0], ow.isCanadian);
                    aPart.price = 0;
                    partsToProcess.add(aPart);
                } else {
                    return false;
                }
            }

            PriceBookEntry[] pbes;
            if(Test.isRunningTest()) {
                pbes = [SELECT Product2Id FROM PriceBookEntry WHERE Pricebook2Id = :(Test.getStandardPricebookId()) AND isDeleted = FALSE LIMIT 50000];
            }
            else {
               //pbes = [SELECT Product2Id FROM PriceBookEntry WHERE PriceBook2.isStandard = TRUE AND isDeleted = FALSE AND IsActive = True LIMIT 50000];
                pbes = [SELECT Product2Id FROM PriceBookEntry WHERE PriceBook2.Name = 'Standard Price Book' AND isDeleted = FALSE AND IsActive = True and Product2.Family = 'First Alert'];
            }
            Map<Id, PriceBookEntry> pbeByProdId = new Map<Id, PriceBookEntry>();
            for(PriceBookEntry pbe : pbes) {
                pbeByProdId.put(pbe.Product2Id, pbe);
            }            
            OrderItem[] ois = new OrderItem[]{};
            system.debug('pbes-->'+JSON.serialize(pbes));
            system.debug('partsToProcess-->'+JSON.serialize(partsToProcess));
            system.debug('pbeByProdId-->'+JSON.serialize(pbeByProdId));
            for(Part p : partsToProcess) {
               /* if(!pbeByProdId.containsKey(p.p.Id)) {
                    throw new AuraHandledException('No Standard price defined for order item' + p.p.Name);
                }*/
                if(pbeByProdId.get(p.p.Id) == null) {
                    string errorMessage = 'No Standard price defined for order item';
                    AuraHandledException msg = new AuraHandledException(errorMessage);
                    msg.setMessage(errorMessage);
                    throw msg;
                    // throw new AuraHandledException('No Standard price defined for order item' + p.p.Name);
                }
                System.debug(p);
                OrderItem oi = new OrderItem(
                    OrderId = ord.Id,
                    PricebookEntryId = pbeByProdId.get(p.p.Id).Id,
                    Quantity = p.qty,
                    UnitPrice = p.price,
                    //Stockroom__c = p.stockroom,
                    Notes_Line_1__c = p.getNotesField(0),
                    Notes_Line_2__c = p.getNotesField(1),
                    Notes_Line_3__c = p.getNotesField(2),
                    Notes_Line_4__c = p.getNotesField(3),
                    PreAuth_Qty__c = (getIsAdvancedExchange(ow)) ? p.preAuthQty : 0,
                    PreAuth_Unit_Price__c = (getIsAdvancedExchange(ow)) ? p.preAuthPrice : 0,
                    PreAuth_Total_Price__c = (getIsAdvancedExchange(ow)) ? p.preAuthQty * p.preAuthPrice : 0,
                    Batteries_Or_Wire_Harness__c = p.batteriesOrWireHarness == 'Yes'
                );
                ois.add(oi); 
            }

            Boolean canadaOrder = false;
            system.debug('ord-->'+JSON.serialize(ord));
            Order[] ordType = [SELECT RecordType.DeveloperName FROM Order WHERE Id = :ord.Id];
             system.debug('ordType-->'+JSON.serialize(ordType));

            if(!ordType.isEmpty() && ordType[0].RecordTypeId != null && ordType[0].RecordType.DeveloperName == 'Canada_Order')
            canadaOrder = true;
            system.debug('canadaOrder-->'+canadaOrder);
            if(ord.Waive_Shipping_Fee__c != true && canadaOrder == false) {
                PriceBookEntry[] shPbe = [SELECT Id FROM PriceBookEntry WHERE Product2.Name = 'Shipping and Handling' LIMIT 1];
                system.debug('shPbe-->'+JSON.serialize(shPbe));
                if(!shPbe.isEmpty()) {
                    OrderItem sh = new OrderItem(
                        OrderId = ord.Id,
                        PricebookEntryId = shPbe[0].Id,
                        Quantity = 1,
                        UnitPrice = ow.Shipping
                        );
                    ois.add(sh);
                }
            }
            system.debug('ois-->'+JSON.serialize(ois));
            insert ois;
            return true;
        }
        catch(Exception e) {
            System.debug(e);
            throw new AuraHandledException(e.getMessage());
        }
    }

    private static string createReturn(Order ord, OrderWrapper ow) {
        WorkOrder workOrd = new WorkOrder();

         try {

            workOrd.AccountId = ow.AccountId;
            workOrd.CaseId = ow.Id;
            workOrd.ContactId = ow.AccountContactId;

            if(ord.Id != null) {
                workOrd.Order__c = ord.Id;
            }
            if(ord.Id != null) {
                workOrd.OwnerId = [SELECT Id,OwnerId FROM Order Where ID = :ord.Id LIMIT 1][0].OwnerId;
            }
            workOrd.Street = ow.ShippingStreet;
            workOrd.City = ow.ShippingCity;
            workOrd.State = ow.ShippingState;
            workOrd.PostalCode = ow.ShippingPostalCode;
            workOrd.Country = ow.ShippingCountry;
            workOrd.Return_Needed__c = ow.ReturnNeeded == 'true' ? 'yes' : 'no';
            workOrd.Return_Type__c = ow.ReturnNeeded == 'true' ? ow.ReturnType : 'None';
            workOrd.Return_Only__c = ow.ReturnType == 'Return Only';
            workOrd.Pricebook2Id = ow.PriceBookId;
            workOrd.CaseId = ow.Id;

            if(String.isBlank(ow.CompanyName)) {
                workOrd.Company_Person_Name__c = ow.ContactName;
            }
            else {
                workOrd.Contact_Attention__c = ow.CompanyName;
                workOrd.Company_Person_Name__c = ow.ContactName;
            }
    
            workOrd.Contact_Email__c = ow.Email;
            
            Account a = [SELECT Id, Phone  FROM Account WHERE Id =:ow.AccountId LIMIT 1];

            
            workOrd.Contact_Phone__c = ow.Phone;
            if(String.isBlank(workOrd.Contact_Phone__c)) {
                workOrd.Contact_Phone__c =a.Phone ;
                //FABASE Migration Change
                /*if(String.isBlank(workOrd.Contact_Phone__c)) {
                    workOrd.Contact_Phone__c = a.PersonHomePhone;
                }*/
            }

            insert workOrd;

            WorkOrderLineItem[] wolis = new WorkOrderLineItem[]{};
            for(Part p : ow.ReturnLineItems) {
                if(p.qty > 0) {
                    WorkOrderLineItem woli = new WorkOrderLineItem(
                        WorkOrderId = workOrd.Id,
                        Case__c = workOrd.CaseId,
                        Qty_Expected__c = p.qty,
                        Product2__c = p.p.Id,
                        Preferred_Return_Carrier__c = p.p.Preferred_Return_Carrier__c,
                        Contact_Email__c = ord.Billing_Email__c,
                        Street = workOrd.Street,
                        City = workOrd.City,
                        State = workOrd.State,
                        PostalCode = workOrd.PostalCode,
                        Country = workOrd.Country,                        
                        Description = p.notes
                    );
                    if(ord.Id != null) {
                        woli.OrderId = ord.Id;
                    }

                    if(!p.p.Disposal_Product__c)
                        woli.PricebookEntryId = p.pbe.Id;

                    wolis.add(woli);
                }
            }
            insert wolis;
            System.debug('return created: ' + workOrd.Id);

            return workOrd.Id;
        }
        catch(Exception e){
            system.debug('return not created. see error.');
            System.debug(e.getMessage());
            return '';
        }
    }

    private static LightningSelect[] getRecordTypes(){
        LightningSelect[] results = new LightningSelect[]{};
        
        Map<String,Schema.RecordTypeInfo> rts = new Map<String,Schema.RecordTypeInfo>();
        rts = SObjectType.Case.getRecordTypeInfosByDeveloperName();

        for(String rt: rts.keySet()) {
            if(rts.get(rt).getName() != 'Misdirected/Unknown' && rts.get(rt).getName() != 'Master' && rts.get(rt).getName() != 'Other' && rts.get(rt).getName() != 'Interaction')
                results.add(new LightningSelect(rts.get(rt).getRecordTypeId(), rts.get(rt).getName()));
        } 

        System.debug('returning record types' );
        
        return results;
    }

    @TestVisible
    private static OrderWrapper createOrderWrapper(String caseId){
        OrderWrapper ow = new OrderWrapper(caseId);
        Case c;
        Id productId;
        if(String.isNotEmpty(caseId)){
            c = [SELECT Id, CaseNumber, AccountId, RecordType.DeveloperName, Account.Name, Contact.Name, Contact.Email , Contact.Phone,  Contact.MailingStreet, Contact.MailingCity, 
                 Contact.MailingState, Contact.MailingPostalCode, Contact.MailingCountry ,Contact.Id, Asset.Product2.Stockroom__c, Asset.Product2.Physical_Stock__c, Asset.Product2.Orderable__c, 
                 Asset.Product2.Case_Creation__c,Asset.Product2.IsActive, Asset.Product2.Name, Asset.Product2.Description, Asset.Product2.Product_Image__c, Asset.Product2.RecordType.Name, 
                 Asset.Product2.RecordType.DeveloperName, Asset.Product2.Family, Asset.Product2.Available_Balance__c, Asset.Product2.In_Transit__c, Asset.Product2.On_Order__c, Asset.Product2.Recall_Count__c, 
                 Asset.Product2.Order_Override__c, Asset.Product2.Has_Replacement__c, Asset.Product2.Canada_Stock_Available__c, Asset.Product2.Part_Type__c, Asset.Product2.Preferred_Return_Carrier__c, Asset.Product2.Product_Region__c, Type 
                 FROM Case Where Id =:caseId LIMIT 1]; //FABase Migration- Removed 'Account.PersonContact.Id' from Query and All PersonAccount related fields replaced with Contact fields
system.debug('CaseRecord--> '+JSON.serialize(c));

            if(c != null){
                // Fill out case info
                ow.Id = caseId;
                ow.AccountId = c.AccountId; 
                //FABase Migration- Commented the below line as PersonAccount is not being used and replaced Account with Contact fields to be displayed in UI
                ow.AccountContactId = c.Contact.Id;
                ow.PriceBookId = (Test.isRunningTest()) ? Test.getStandardPricebookId() : [SELECT Id FROM PriceBook2 WHERE isStandard = TRUE LIMIT 1][0].Id;
                ow.CaseNumber = c.CaseNumber;
                ow.CompanyName = c.Account.Name; 
                ow.ContactName = c.Contact.Name;
                ow.Phone = c.Contact.Phone;
                ow.Email = c.Contact.Email;
                ow.ShippingStreet = c.Contact.MailingStreet;
                ow.ShippingCity = c.Contact.MailingCity;
                ow.ShippingState = c.Contact.MailingState;
                ow.ShippingPostalCode = c.Contact.MailingPostalCode;
                ow.ShippingCountry = c.Contact.MailingCountry ;
                if(c.Type  != null)
                    ow.isSpecialHandling = c.Type.equals('Special Handling');

                System.debug('isSpecialHandling: ' + ow.isSpecialHandling);

                if(c.Account != null && (String.isBlank(c.Contact.MailingCountry ) || c.Contact.MailingCountry  == 'CA'))
                    ow.isCanadian = true;

                if(c.Asset.Product2Id != null)
                    productId = c.Asset.Product2Id;
system.debug('ow--> '+JSON.serialize(ow));
                loadProduct(ow, c);
                   
            } else {
                System.debug('There was no case associated with the passed Id: ' + caseId);
            }
        }

        User u = [SELECT Id, LID_Code__c FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1];

        String bypassAV = ApexCodeSettings__c.getOrgDefaults().Bypass_Address_Validation__c;

        System.debug('bypassAV: ' + byPassAv);
        System.debug('userId: ' + u.Id);


        if(String.isNotEmpty(bypassAV)){
            if(bypassAv.contains(u.Id.to15())){
                System.debug('user can bypass av');
                ow.CanBypassAddressValidation = true;
            }
        }

        ow.UserLocation = u.LID_Code__c;

        return ow;
    }

    private static Map<String, LightningSelect[]> getStates(){
        Map<String, LightningSelect[]> stateOptionsMap = new Map<String, LightningSelect[]>();

        for(PicklistEntry entry : SObjectType.Contact.fields.StateOption__c.getPicklistValues()){
            stateOptionsMap.put(entry.getValue(), new LightningSelect[]{new LightningSelect('', '--Select State--')});
        }

        return stateOptionsMap;
    }

    private static LightningSelect[] getCountries(){
        LightningSelect[] results = new LightningSelect[]{};
        LightningSelect[] orderedResults = new LightningSelect[]{new LightningSelect('', '--Select Country--')};

        for(PicklistEntry entry : SObjectType.Contact.fields.CountryOption__c.getPicklistValues()) {
            if(entry.getLabel() == 'United States') {
                orderedResults.add(new LightningSelect(entry.getValue(), entry.getLabel()));
            } else {
                results.add(new LightningSelect(entry.getValue(), entry.getLabel()));
            }
        }

        for(LightningSelect ls : results) {
            orderedResults.add(ls);
        }

        return orderedResults;
    }

    private static LightningSelect[] getCountry(){
        LightningSelect[] orderedResults = new LightningSelect[]{new LightningSelect('', '--Select Country--')};

         //orderedResults.add(new LightningSelect('United States', 'United States'));
         //orderedResults.add(new LightningSelect('Canada', 'Canada'));
			List<CountryList__c> mcs = CountryList__c.getall().values();
        for(CountryList__c c : mcs){
             orderedResults.add(new LightningSelect(c.Name, c.Country_Name__c));
        }
        return orderedResults;
    }

    private static void loadProduct(OrderWrapper ow, Case c) {
        Part[] products = new Part[]{};
        
        //add case asset and related products to returns page
        if(c.Asset.Product2.Orderable__c || c.Asset.Product2.Case_Creation__c) {
            ow.ReturnParts = new list<Part>();
            
            //asset
            Part retProduct = new Part(c.Asset.Product2, ow.isCanadian);      
            if(retProduct.p.RecordType.DeveloperName == 'Amer_Products') {
               ow.ReturnParts.add(retProduct.clone()); 
               
                //parts
                for(Part p : getParts(new Set<Id>{retProduct.p.Id}, ow.isCanadian).get(retProduct.p.Id)) {
                    system.debug('689');
                    if(p.p.RecordType.DeveloperName == 'Amer_Products') {
                        ow.ReturnParts.add(p);
                    }
                }
            }           
        }
system.debug(' ow.ReturnParts--> '+JSON.serialize(ow.ReturnParts));
        if((c.Asset.Product2.Orderable__c || c.Asset.Product2.Case_Creation__c) && c.Asset.Product2.Stockroom__c != 'None' /* && cs.Asset.Product2.Has_Replacement__c == false */) {
            ow.Product = new Part(c.Asset.Product2, ow.isCanadian);
            products = new Part[] { ow.Product };
        }

        if(c.Asset != null){
            Replacement__c[] replacements = [
                SELECT Replacement_Product__r.Stockroom__c, Replacement_Product__r.Orderable__c, Replacement_Product__r.Physical_Stock__c, Replacement_Product__r.Name, Replacement_Product__r.Description, Replacement_Product__r.Product_Image__c, Replacement_Product__r.RecordType.Name, Replacement_Product__r.RecordType.DeveloperName, Replacement_Product__r.Family, Replacement_Product__r.Available_Balance__c, Replacement_Product__r.In_Transit__c, Replacement_Product__r.On_Order__c, Replacement_Product__r.Recall_Count__c, Replacement_Product__r.Order_Override__c, Replacement_Product__r.Has_Replacement__c, Replacement_Product__r.Canada_Stock_Available__c, Replacement_Product__r.Part_Type__c, Replacement_Product__r.Preferred_Return_Carrier__c, Replacement_Product__r.Product_Region__c
                FROM Replacement__c
                WHERE Replacement_Product__r.Orderable__c = TRUE AND Obsolete_Product__c = :c.Asset.Product2Id /* AND Replacement_Product__r.Has_Replacement__c = FALSE */
                ORDER BY Replacement_Product__r.Name ASC
            ];
            system.debug(' replacements--> '+JSON.serialize(replacements));
            for(Replacement__c replacement : replacements){
                System.debug('708');
                products.add(new Part(replacement.Replacement_Product__r, ow.isCanadian));
            }
        }
system.debug(' products--> '+JSON.serialize(products));
        Set<Id> prodIds = new Set<Id>{};
        for(Part p : products)
            prodIds.add(p.p.Id);

        //Get parts
        Map<Id, Part[]> productParts = getParts(prodIds, ow.isCanadian);
system.debug(' productParts--> '+JSON.serialize(productParts));
        //Group parts under each product
        ow.Parts = new Part[]{};
        for(Part prod : products) {
            ow.Parts.add(prod);
            for(Part p : productParts.get(prod.p.Id))
            ow.Parts.add(p);   
            
        }
        system.debug(' ow.Parts--> '+JSON.serialize(ow.Parts));
        loadPriceData(ow.Parts);
    }

    private static void loadPriceData(Part[] parts) {
        Set<Id> pIds = new Set<Id>();
        system.debug('parts-->'+JSON.serialize(parts));
        for(Part p : parts) {
            pIds.add(p.p.Id);
        }
        system.debug('pIds-->'+JSON.serialize(pIds));
        PricebookEntry[] pbes;
        if(Test.isRunningTest()) {
            pbes = [SELECT Pricebook2Id, Product2Id, UnitPrice, PreAuth_Needed__c, PreAuth_List_Price__c FROM PricebookEntry WHERE Product2Id IN :pIds AND PriceBook2Id = :(Test.getStandardPricebookId()) LIMIT 10000];
        }
        else {
            pbes = [SELECT Pricebook2Id, Product2Id, UnitPrice, PreAuth_Needed__c, PreAuth_List_Price__c FROM PricebookEntry WHERE Product2Id IN :pIds AND PriceBook2.isStandard = TRUE LIMIT 10000];
        }
        system.debug('pbes-->'+JSON.serialize(pbes));
        Map<Id, PricebookEntry> productToPbEntryMap = new Map<Id, PricebookEntry>();
        for(PricebookEntry pbe : pbes) {
            productToPbEntryMap.put(pbe.Product2Id, pbe);
        }
        system.debug('productToPbEntryMap-->'+JSON.serialize(productToPbEntryMap));
        for(Part p : parts) {
            if(productToPbEntryMap.containsKey(p.p.Id)){
                PricebookEntry e = productToPbEntryMap.get(p.p.Id);
                p.preAuthNeeded = e.PreAuth_Needed__c;
                p.preAuthPrice = (p.preAuthNeeded && e.PreAuth_List_Price__c != null) ? e.PreAuth_List_Price__c : 0;
                p.preAuthQty = (p.preAuthNeeded) ? 1 : 0;
                p.price = e.UnitPrice;
                p.pbe = e;
                System.debug(e);
            }           
        }
    }
    //FABASE Migration change
    /*private static void loadReplacements(List<Part> parts){
        Set<Id> productIds = new Set<Id>();

        for(Part p : parts){
            if(p.p.RecordType.Name == 'Product'){
                productIds.add(p.p.Id);
            }
        }

        Map<Id, List<Alternative__c>> replacementsByProdId = new Map<Id, List<Alternative__c>>();

        Date today = Date.today();

        for(Alternative__c a : [SELECT Id, Original_Product__c, Alternative_Product__c FROM Alternative__c WHERE Alternative_Expiration_Date__c > :today AND Original_Product__c IN :productIds]){
            List<Alternative__c> alts = new List<Alternative__c>();

            if(replacementsByProdId.containsKey(a.Original_Product__c)){
                alts = replacementsByProdId.get(a.Original_Product__c);
            }

            alts.add(a);

            replacementsByProdId.put(a.Original_Product__c, alts);
        }
    }*/

    @TestVisible
    private static List<Part> loadDisposalParts() {
        Part[] products = new Part[]{};
        
        for(Product2 p : [SELECT Id, Physical_Stock__c, Orderable__c, Stockroom__c,
        Case_Creation__c,IsActive, Name, Description, Product_Image__c, RecordType.Name, 
        RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Recall_Count__c, 
        Order_Override__c, Has_Replacement__c, Canada_Stock_Available__c, Part_Type__c, Preferred_Return_Carrier__c, Product_Region__c, Disposal_Product__c
        FROM Product2
        WHERE Disposal_Product__c = true]){
            products.add(new Part(p, false));
        }

        return products;        
    }



    private static List<Part> searchDisposalParts(String searchTerm) { 

        Part[] products = new Part[]{};
        
        for(Product2 p : [SELECT Id, Physical_Stock__c, Orderable__c, Stockroom__c,
        Case_Creation__c,IsActive, Name, Description, Product_Image__c, RecordType.Name, 
        RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Recall_Count__c, 
        Order_Override__c, Has_Replacement__c, Canada_Stock_Available__c, Part_Type__c, Preferred_Return_Carrier__c, Product_Region__c, Disposal_Product__c
        FROM Product2
        WHERE Disposal_Product__c = true AND Name LIKE :searchTerm]){
            products.add(new Part(p, false));
        }

        return products;        
    }

    private static Boolean isPOBox(String street){
        return String.isNotBlank(street) && Pattern.matches(PO_BOX_EXPR, street);
    }

    public static Boolean getIsAdvancedExchange(OrderWrapper ow) {
        return ow.ReturnType == 'Advanced Exchange' &&  getIsReturn(ow);
    }

    public static Boolean getIsReturn(OrderWrapper ow) {
        return ow.ReturnNeeded == 'true';
    }     

    private static Map<Id, Part[]> getParts(Set<Id> productIds, Boolean isCanadian) {
        Product_Part__c[] prodParts = [
            SELECT Product__c,
                   Product_Part__r.Name,
                   Product_Part__r.Description,
                   Product_Part__r.Product_Image__c,
                   Product_Part__r.RecordType.Name,
                   Product_Part__r.RecordType.DeveloperName,
                   Product_Part__r.Family,
                   Product_Part__r.Available_Balance__c,
                   Product_Part__r.In_Transit__c,
                   Product_Part__r.On_Order__c,
                   Product_Part__r.Stockroom__c,
                   Product_Part__r.Physical_Stock__c,
                   Product_Part__r.Has_Replacement__c,
                   Product_Part__r.Recall_Count__c,
                   Product_Part__r.Order_Override__c,
                   Product_Part__r.Canada_Stock_Available__c,
                   Product_Part__r.Orderable__c,
                   Product_Part__r.Part_Type__c,
                   Product_Part__r.Product_Region__c
            FROM Product_Part__c
            WHERE Product_Part__r.Orderable__c = TRUE AND Product__c IN :productIds AND Product_Part__c != null /*AND (Product_Part__r.Recall_Count__c = NULL OR Product_Part__r.Recall_Count__c = 0)*/ AND Product_Part__r.Stockroom__c != 'None' /*AND (Product_Part__r.Replacement__c = null OR Product_Part__r.Replacement__r.Stockroom__c = 'None')*/
            ORDER BY Product_Part__r.Name
        ];
system.debug('prodParts--> '+JSON.serialize(prodParts));
        Map<Id, Part[]> partsByProductId = new Map<Id, Part[]>();
        for(Id pid : productIds)
            partsByProductId.put(pid, new Part[]{});

        Set<Id> partsAdded = new Set<Id>(); //Used to prevent same part from being listed twice
        for(Product_Part__c part : prodParts) {
            if(!partsAdded.contains(part.Product_Part__c)) {
                partsByProductId.get(part.Product__c).add(new Part(part.Product_Part__r, isCanadian));
                partsAdded.add(part.Product_Part__c);
            }
        }
system.debug('partsByProductId--> '+JSON.serialize(partsByProductId));
        return partsByProductId;
    }

    public class OrderWrapper{
        public String Id { get; set; }
        public String AccountId { get; set; }
        public String AccountContactId { get; set; }
        public String PriceBookId { get; set; }
        public String CaseNumber { get; set; }
        public String ContactName { get; set; }
        public String Phone { get; set; }
        public String CompanyName { get; set; }
        public String Email { get; set; }
        public String ShippingStreet { get; set; }
        public String ShippingCity { get; set; }
        public String ShippingState { get; set; }
        public String ShippingPostalCode { get; set; }
        public String ShippingCountry { get; set; }
        public Boolean isCanadian { get; set; }
        public Part Product { get; set; }
        public Part refundProduct { get; set; }
        public String UserLocation { get; set; }

        public String Type { get; set; }
        public String ReturnNeeded { get; set; }
        public String ReturnType { get; set; }

        public String SearchTerm { get; set; }

        public Boolean SendPaymentRequest { get; set; }
        public Boolean isSpecialHandling { get; set; }
        public Boolean CreateShippingLabel { get; set; }
        public Boolean ExpediteShipping { get; set; }
        public Boolean WaiveShipping { get; set; }
        public Boolean UpdateShippingAddress { get; set; }
        public Boolean CanBypassAddressValidation { get; set; }

        public List<Part> OrderLineItems { get; set; }
        public List<Part> ReturnLineItems { get; set; }
        public List<Part> Parts { get; set; }
        public List<Part> ReturnParts { get; set; }

        public Decimal Shipping { get; set; }

        public OrderWrapper(String caseId){
            this.Id = caseId;
            this.Type = '';
            this.ReturnType = '';
            this.ReturnNeeded = '';
            this.isCanadian = false;
            this.SendPaymentRequest = false;
            this.CreateShippingLabel = false;
            this.ExpediteShipping = false;
            this.WaiveShipping = false;
            this.UpdateShippingAddress = true;
            this.OrderLineItems = new List<Part>();
            this.ReturnLineItems = new List<Part>();
            this.Shipping = 0.00;
            this.CanBypassAddressValidation = false;
            this.isSpecialHandling = false;

            List<Product2> p = [SELECT IsActive, Orderable__c, Name, Description, Product_Image__c, RecordType.Name, RecordType.DeveloperName, Family, Available_Balance__c, In_Transit__c, On_Order__c, Stockroom__c, Physical_Stock__c, Has_Replacement__c, Recall_Count__c, Order_Override__c, Canada_Stock_Available__c, Part_Type__c,Preferred_Return_Carrier__c, Product_Region__c FROM Product2 WHERE Id = :REFUND_REQUEST_PRODUCT_ID LIMIT 1];
            if(!p.isEmpty()) {
                Part aPart = new Part(p[0], false);
                aPart.price = 0;
                this.refundProduct = aPart;
            }
            else {
                System.debug('refund part could not be found');
                this.refundProduct = null;
            }

        }
    }

    public class Part {
        private Boolean isCanadian { get; set; }
        public Product2 p {get; set;}
        public Purchase_Order__c[] pos {get; set;}
        public Decimal price {get; set;}
        public Integer qty {get; set;}
        //public String stockroom { get; set; }
        public Decimal total { get; set; }
        public String notes {get; set;}
        public Boolean preAuthNeeded {get;set;}
        public Integer preAuthQty {get;set;}
        public Decimal preAuthPrice {get;set;}
        public Boolean isUniversalPart {get;set;}
        public String batteriesOrWireHarness {get;set;}
        public PricebookEntry pbe {get;set;}
        public Boolean isSelected { get; set; }
        public Boolean canSelect { get; set; }
        public String unavailableReason { get; set; }
        public String region { get; set; }
        public List<Part> replacements { get; set; }

        @TestVisible
        Part(Product2 prod, Boolean isCanadian) {
            this.isCanadian = isCanadian;
            this.p = prod;
            this.price = price;
            this.qty = 1;
            //this.stockroom = prod.Stockroom__c;
            //total = qty * price;
            this.preAuthNeeded = false;
            this.preAuthQty = 0;
            this.preAuthPrice = 0;
            this.isUniversalPart = prod.Name.toLowerCase().contains('universal');
            this.batteriesOrWireHarness = '';
            this.isSelected = false;
            this.canSelect = this.getCanSelect(prod);
            this.unavailableReason = this.getUnavailableReason(prod);
            this.region = prod.Product_Region__c;
        }

        public String getNotesField(Integer row) {
            if(this.notes == null)
                return null;

            String[] lines = this.notes.split('\n');
            if(row >= lines.size())
                return null;

            return lines[row].left(35);
        }

        public Boolean getCanSelect(Product2 p) {
            
            if(!p.Orderable__c)
                return false;
            
            if(p.Order_Override__c)
                return true;
            
            if(p.Physical_Stock__c > 0)
                return true;
            
            if(p.Available_Balance__c <= 0 && p.Physical_Stock__c <= 0 && !p.Order_Override__c)
                return false;

            if(p.Recall_Count__c != null && p.Recall_Count__c > 0)
                return false;

            if(p.Available_Balance__c <= 0 && p.Has_Replacement__c == true)
                return false;

            /*
            if(p.Available_Balance__c <= 0)
                return false;
            */
            
            if(p.Stockroom__c != 'en' || ApexCodeSettings__c.getOrgDefaults().NewOrderValidateAvailableBalance__c != true)
                return true;

            return (p.Available_Balance__c != null && p.Available_Balance__c > 0) || (p.In_Transit__c != null && p.In_Transit__c > 0) ||
                   (p.On_Order__c != null && p.On_Order__c > 0) || (isCanadian && p.Canada_Stock_Available__c);

        }

        public String getUnavailableReason(Product2 p) {
            if(p.Recall_Count__c != null && p.Recall_Count__c > 0)
                return 'recalled';
            if(p.Has_Replacement__c == true)
                return 'obsolete';
            return 'not in stock';
        }
    }

    private static final Map<String, String> STATE_MAP = new Map<String, String> {
        'AL' => 'Alabama', 
        'AK' => 'Alaska', 
        'AZ' => 'Arizona', 
        'AR' => 'Arkansas', 
        'CA' => 'California', 
        'CO' => 'Colorado', 
        'CT' => 'Connecticut', 
        'DE' => 'Delaware', 
        'FL' => 'Florida', 
        'GA' => 'Georgia', 
        'HI' => 'Hawaii', 
        'ID' => 'Idaho', 
        'IL' => 'Illinois', 
        'IN' => 'Indiana', 
        'IA' => 'Iowa', 
        'KS' => 'Kansas', 
        'KY' => 'Kentucky', 
        'LA' => 'Louisiana', 
        'ME' => 'Maine', 
        'MD' => 'Maryland', 
        'MA' => 'Massachusetts', 
        'MI' => 'Michigan', 
        'MN' => 'Minnesota', 
        'MS' => 'Mississippi', 
        'MO' => 'Missouri',
        'MT' => 'Montana', 
        'NE' => 'Nebraska', 
        'NV' => 'Nevada', 
        'NH' => 'New Hampshire', 
        'NJ' => 'New Jersey', 
        'NM' => 'New Mexico', 
        'NY' => 'New York', 
        'NC' => 'North Carolina', 
        'ND' => 'North Dakota', 
        'OH' => 'Ohio', 
        'OK' => 'Oklahoma', 
        'OR' => 'Oregon', 
        'PA' => 'Pennsylvania', 
        'RI' => 'Rhode Island', 
        'SC' => 'South Carolina', 
        'SD' => 'South Dakota', 
        'TN' => 'Tennessee', 
        'TX' => 'Texas', 
        'UT' => 'Utah', 
        'VT' => 'Vermont', 
        'VA' => 'Virginia', 
        'WA' => 'Washington', 
        'WV' => 'West Virginia', 
        'WI' => 'Wisconsin', 
        'WY' => 'Wyoming'
    };

    public static Map<String, String> lookupCityState(String zip, Boolean fullStateName) {
        Map<String, String> info = new Map<String, String> {'city' => '', 'state' => ''};
        
        if(zip == null)
            return info;

        //Canada Postal Code
        if(Pattern.matches('^[ABCEGHJKLMNPRSTVXY]\\d[A-Z] ?\\d[A-Z]\\d$', zip.toUpperCase())) {
            //curl -X GET -H "Accept: application/vnd.cpc.postoffice+xml" -H "Authorization: Basic NjgzZmVjMmIzNGM0YjI4NDozMmIwMzg5ZWIyNmVlNWE0YmM2ZmI2" "https://soa-gw.canadapost.ca/rs/postoffice?postalCode=M6C3Y1&maximum=1"
            zip = zip.toUpperCase().replace(' ', '');

            try {
                String url = 'https://soa-gw.canadapost.ca/rs/postoffice?maximum=1&postalCode=' + zip;
            
                Http h = new Http();
                HttpRequest req = new HttpRequest();
                req.setMethod('GET');
                req.setHeader('Authorization', 'Basic NjgzZmVjMmIzNGM0YjI4NDozMmIwMzg5ZWIyNmVlNWE0YmM2ZmI2');
                req.setHeader('Accept', 'application/vnd.cpc.postoffice+xml');
                req.setEndpoint(url);
                String xml;
                if(!Test.isRunningTest())
                    xml = h.send(req).getBody();
                else
                    xml = ' <CITY>TORONTO</CITY><PROVINCE>ON</PROVINCE>';
                Integer cs = xml.toUpperCase().indexOf('<CITY>');
                Integer ce = xml.toUpperCase().indexOf('</CITY>');
                Integer ss = xml.toUpperCase().indexOf('<PROVINCE>');
                Integer se = xml.toUpperCase().indexOf('</PROVINCE>');
                
                if(cs > 0 && ce > cs && ss > 0 && se > ss) {
                    info.put('city', TitleCaseUtil.toTitleCase(xml.substring(cs + 6, ce).trim(), false));
                    info.put('state', xml.substring(ss + 10, se).trim());

                    if(fullStateName == true && STATE_MAP.containsKey(info.get('state').trim().toUpperCase()))
                        info.put('state', STATE_MAP.get(info.get('state').trim().toUpperCase()));
                }
                
                System.debug(info);
                
                return info;
            }
            catch(Exception e) {
                String message = 'QuickCreateController.lookupCityState() threw: ' + e.getMessage() + ' @ ' + e.getStackTraceString();
                System.debug(message);
                sendErrorReport(message);
                return info;
            }


            //return null;
        }
        else { //US Zip Code
            zip = zip.replaceAll('[^0-9]', '');
            zip = zip.left(5);
            if(zip.length() != 5)
                return info;
            
            try {
                String url = 'http://production.shippingapis.com/ShippingAPI.dll';
                url += '?API=CityStateLookup&XML=' + EncodingUtil.urlEncode('<CityStateLookupRequest USERID="844JARDE1026"><ZipCode ID="0"><Zip5>' + String.escapeSingleQuotes(zip) + '</Zip5></ZipCode></CityStateLookupRequest>', 'UTF-8');
            
                Http h = new Http();
                HttpRequest req = new HttpRequest();
                req.setMethod('GET');
                req.setEndpoint(url);
                String xml;
                if(!Test.isRunningTest())
                    xml = h.send(req).getBody();
                else
                    xml = ' <City>TRENTON</City><State>NJ</State>';
                Integer cs = xml.toUpperCase().indexOf('<CITY>');
                Integer ce = xml.toUpperCase().indexOf('</CITY>');
                Integer ss = xml.toUpperCase().indexOf('<STATE>');
                Integer se = xml.toUpperCase().indexOf('</STATE>');
                
                if(cs > 0 && ce > cs && ss > 0 && se > ss) {
                    info.put('city', TitleCaseUtil.toTitleCase(xml.substring(cs + 6, ce).trim(), false));
                    info.put('state', xml.substring(ss + 7, se).trim());

                    if(fullStateName == true && STATE_MAP.containsKey(info.get('state').trim().toUpperCase()))
                        info.put('state', STATE_MAP.get(info.get('state').trim().toUpperCase()));
                }
                
                System.debug(info);
                
                return info;
            }
            catch(Exception e) {
                String message = 'QuickCreateController.lookupCityState() threw: ' + e.getMessage() + ' @ ' + e.getStackTraceString();
                System.debug(message);
                sendErrorReport(message);
                return info;
            }
        }
    }

    
    private static void sendErrorReport(String details) {
        try {
            ApexCodeSettings__c setting = ApexCodeSettings__c.getOrgDefaults();
            if(String.isBlank(details) || String.isBlank(setting.QuickCreateErrorEmailUserId__c))
                return;
                
            if(String.isNotBlank(setting.QuickCreateErrorEmailExclusions__c))
                for(String exclusion : setting.QuickCreateErrorEmailExclusions__c.trim().split('\n'))
                    if(String.isNotBlank(exclusion))
                        if(details.contains(exclusion.trim()))
                            return;
    
            Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
            msg.setPlainTextBody(details);
            msg.setTargetObjectId(setting.QuickCreateErrorEmailUserId__c.trim());
            msg.setSaveAsActivity(false);
            
            for(Messaging.SendEmailResult result : messaging.sendEmail(new Messaging.Email[]{msg}, false))
                if(!result.isSuccess())
                    for(Messaging.SendEmailError err : result.getErrors())
                        System.debug(err.getMessage());
        }
        catch(Exception e) {
            System.debug('OrderAndReturnCtrl.sendErrorReport threw: ' + e.getMessage() + ' @ ' + e.getStackTraceString());
        }
    }

    //FOR PICKLISTS
    public class LightningSelect{
    
        public String value{get;set;}
        public String label{get;set;}
        public String icon{get;set;}
    
        public LightningSelect(String value, String label){
            this.value = value;
            this.label = label;
        }

        public LightningSelect(String value, String label, String icon){
            this.value = value;
            this.label = label;
            this.icon = icon;
        }
    }
}