PurposeCode.java

package com.tradecloud.domain.configuration.clearing.za;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public enum PurposeCode {

    HOME_USE_ORDINARY_LEVY("Home Use - Ordinary Levy ",
            "A", 10,
            ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    HOME_USE_DUTY_PAID("Home Use - Duty Paid",
            "A", 11,
            ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    HOME_USE_GENERAL_REBATE("Home Use - General Rebate",
            "A", 12, ProcessType.CCAi, 00),
    HOME_USE_WARENTY("Home Use - Warenty Replacement",
            "A", 13,
            ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    HOME_USE_SPECIFIC_SCH4("Home Use - specific conditions under Schedule 4",
            "A", 14,
            ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    ABANDONMENT_DESTRUCTION("Goods offered for unconditional abandonment and/or destruction",
            "A", 15,
            ProcessType.IMP, 00, 14, 20, 35, 40, 41, 42, 44, 48, 80, 85),

    NATIONAL_AND_INTERNATIONAL_TRANSIT_BOND("National Transit of goods removed 'in bond' from  a 'customs controlled area' at port/place of entry to " +
            "a 'customs controlled area' within the Republic",
            "B", 20,
            ProcessType.IMP, 00, 75, 90),
    NATIONAL_AND_INTERNATIONAL_TRANSIT_PORT("International Transit of goods removed 'in transit' from a port/place of entry and exported from the " +
            "Republic",
            "B", 21,
            ProcessType.IMP, 00),
    NATIONAL_AND_INTERNATIONAL_TRANSIT_BLNS("International Transit of BLNS goods removed 'in transit' through the Republic",
            "B", 22,
            ProcessType.CCAi, 00),

    TRANSSHIPMENT("Transshipment",
            "C", 30,
            ProcessType.IMP, 00),

    TEMPORARY_ADMISSION("Temporary Admission - Home Use",
            "D", 35,
            ProcessType.IMP, 00, 20),
    TEMPORARY_ADMISSION_EXPORT("Temporary Admission - Export",
            "D", 36,
            ProcessType.EXP, 35),
    TEMPORARY_ADMISSION_RE_EXPORT("Temporary Admission - Re-exported",
            "D", 37,
            ProcessType.CCAi, 00),
    TEMPORARY_ADMISSION_EXPORT_BLNS("Temporary Admission - Export BLNS",
            "D", 38,
            ProcessType.CCAe, 37),

    CUSTOMS_WAREHOUSE("Goods in a customs warehouse under 'Warehousing'",
            "E", 40,
            ProcessType.IMP,
            00, 20),
    CUSTOMS_WAREHOUSE_OWNERSHIP("Change of Ownership of warehouse goods. (No physical movement of goods)",
            "E", 41,
            ProcessType.IMP, 40, 41, 44),
    CUSTOMS_WAREHOUSE_FOR_EXPORT("Goods into a customs warehouse for subsequent exportation from the Republic",
            "E", 42,
            ProcessType.IMP, 00, 20, 37),
    CUSTOMS_WAREHOUSE_REMOVAL("Clearance for Removal of warehouse goods, in bond, from one customs warehouse to another customs warehouse",
            "E", 43,
            ProcessType.IMP, 00, 20, 37),
    CUSTOMS_WAREHOUSE_RE_WAREHOUSING("Clearance for Re-warehousing of warehouse goods, previously removed in bond from one customs warehouse to another",
            "E", 44,
            ProcessType.IMP, 43),
    CUSTOMS_WAREHOUSE_REMOVAL_EXCISE("Clearance for Removal of excise goods from one excise warehouse to another excise warehouse",
            "E", 45,
            ProcessType.IMP, 00, 46, 47),
    CUSTOMS_WAREHOUSE_RE_WAREHOUSING_EXCISE("Clearance for Re-warehousing of excise goods, removed in bond from one excise warehouse to another " +
            "excise warehouse",
            "E", 46,
            ProcessType.IMP, 45),
    CUSTOMS_WAREHOUSE_OWNERSHIP_EXCISE("Clearance for  for Change of Ownership of goods in an Excise Warehouse. (No physical movement of goods)",
            "E", 47,
            ProcessType.IMP, 00, 46),
    CUSTOMS_WAREHOUSE_OWNERSHIP_FROM_EXPORT("Clearance for “Change of Ownership” or “re-warehousing” of goods, previously declared for warehousing " +
            "and export",
            "E", 48,
            ProcessType.IMP, 42, 48, 49),
    CUSTOMS_WAREHOUSE_REMOVAL_FROM_EXPORT("Clearance for removal of goods in bond, previously declared form warehousing for export",
            "E", 49,
            ProcessType.IMP, 42, 48, 49),

    STORES_FREE_CIRCULATION("Supply of 'free circulation goods' cleared under 'Stores' as supplies to a foreign-going vessel, aircraft, or train",
            "F",
            51,
            ProcessType.EXP, 00),
    STORES_EXCISABLE_GOODS("Supply of locally produced excisable goods, cleared under 'Stores' as supplies to a foreign-going vessel, aircraft or train",
            "F", 52,
            ProcessType.EXP, 00, 46, 47),
    STORES_IMPORTED_GOODS("Supply of 'imported goods' cleared under 'Stores' as supplies to a foreign-going vessel, aircraft, or train",
            "F", 53,
            ProcessType.EXP, 40, 41, 42, 44, 48),

    TAX_FREE_SHOP("Tax free Shop",
            "G", 55,
            ProcessType.OTHER,
            00, 41, 42, 44, 46, 47),

    EXPORT_FREE_CIRCULATION("Export of free circulation goods",
            "H", 60, ProcessType.EXP, 00),
    EXPORT_FREE_CIRCULATION_BELN("Export of 'free circulation goods' to BELN states",
            "H", 61, ProcessType.CCAe),
    EXPORT_REFUND("Export for Specific Refund",
            "H", 62, ProcessType.EXP, 11),
    EXPORT_DRAWBACK("Export for Drawback",
            "H", 63, ProcessType.EXP, 11),
    EXPORT_EXCISE_REFUND("Export and refund on Excise",
            "H", 64, ProcessType.EXP),
    EXPORT_AUCTIONED("Export of auctioned goods",
            "H", 65, ProcessType.EXP),
    EXPORT_RE_EXPORT("Re-export of BELN goods",
            "H", 66, ProcessType.CCAe, 12),
    EXPORT_WAREHOUSED("Export of imported goods ex Warehouse",
            "H", 67, ProcessType.EXP, 00, 40, 41, 42, 44, 48),
    EXPORT_EXCISE("Export of excise goods ex Warehouse",
            "H", 68, ProcessType.EXP, 00, 46, 47),
    EXPORT_ENVIRONMENTAL("Export of Environmental Levy Goods",
            "H", 69, ProcessType.EXP, 11, 13),

    TEMPORARY_EXPORT_AND_RE_IMPORTATION_HOME_USE("Temporary export for re-imported unaltered goods for home use",
            "I", 75, ProcessType.EXP),
    TEMPORARY_EXPORT_AND_RE_IMPORTATION_BLNS("Temporary export of goods to the BLNS for re-imported unaltered goods for home use",
            "I", 76, ProcessType.CCAe),
    TEMPORARY_EXPORT_AND_RE_IMPORTATION_IMPORT("Re-importation of goods in same state,  under 'Temporary Export'",
            "I", 77, ProcessType.IMP, 20, 60, 68, 75),
    TEMPORARY_EXPORT_AND_RE_IMPORTATION_RE_IMPORT_BLNS("Re-importation of goods in same state from the BLNS,  under 'Temporary Export'",
            "I", 78, ProcessType.CCAi, 76),

    INWARD_PROCESSING("Inward Processing",
            "J", 80, ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    INWARD_PROCESSING_OWNERSHIP("Transfer of ownership of 'Inward processing' goods",
            "J", 81, ProcessType.OTHER, 80),
    INWARD_PROCESSING_SUBCONTRACTING("Sub-contracting of inward processing operations with no transfer of liability to a sub-contractor" +
            " (Cut-Make-Trim CMT)",
            "J", 82, ProcessType.OTHER, 80),
    INWARD_PROCESSING_EXPORT_COMPENSATION("Export of 'inward processed compensating products' (including any waste/scrap) obtained from goods imported under 'Inward Processing'",
            "J", 83, ProcessType.EXP, 80),

    PROCESSING_FOR_HOME_USE("Processing for Home use",
            "K", 85, ProcessType.IMP, 00, 20, 40, 41, 42, 44, 48),
    PROCESSING_FOR_HOME_USE_OWNERSHIP("Transfer of ownership of 'Processing for home use' goods",
            "K", 86, ProcessType.OTHER, 85),
    PROCESSING_FOR_HOME_USE_SUBCONTRACTION("Sub-contracting of processing for home use operations with no transfer of liability to a sub-contractor. " +
            "(Cut-Make-Trim CMT)",
            "K", 87, ProcessType.OTHER, 85),
    PROCESSING_FOR_HOME_USE_COMPENSATION("'Home Use of 'compensating Products' (including and valuable waste/scrap) obtained from goods imported " +
            "under 'Processing for Home Use'",
            "K", 85, ProcessType.OTHER, 85),

    OUTWARD_PROCESSING("Outward Processing",
            "L", 90, ProcessType.EXP, 00),
    OUTWARD_PROCESSING_COMPENSATION("'Home Use' of 'outward processed compensating products' derived from goods exported under 'Outward Processing'",
            "L", 91, ProcessType.IMP, 20, 90),

    TEMPORARY_EXPORT("Temporary export for subsequent re-importation", "I", 75, ProcessType.EXP),
    CLEARANCE_FOR_RE_IMPORT("Clearance for re-importation", "I", 77, ProcessType.IMP);

    private final String value;
    private final String sarsCode;
    private final int cpcCode;
    private final ProcessType type;
    private final Integer[] allowedFrom;

    PurposeCode(String value, String sarsCode, int cpcCode, ProcessType type, Integer... allowedFrom) {
        this.value = value;
        this.sarsCode = sarsCode;
        this.cpcCode = cpcCode;
        this.type = type;
        this.allowedFrom = allowedFrom;
    }

    public String value() {
        return value;
    }

    public static PurposeCode fromValue(String v) {
        for (PurposeCode c : PurposeCode.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }

    public static PurposeCode fromSarsCode(String code) {
        for (PurposeCode c : PurposeCode.values()) {
            if (c.sarsCode.equals(code)) {
                return c;
            }
        }
        throw new IllegalArgumentException(code);
    }

    public String getSarsCode() {
        return sarsCode;
    }

    public int getCpcCode() {
        return cpcCode;
    }

    public String getLabel() {
        return sarsCode + " - " + cpcCode + " " + value;
    }

    public String getShortLabel() {
        return sarsCode + " - " + cpcCode;
    }

    public ProcessType getType() {
        return type;
    }

    public List<Integer> getAllowedFrom() {
        return Arrays.stream(allowedFrom).toList();
    }

    public static List<PurposeCode> forType(ProcessType type) {
        return Arrays.stream(values()).filter(p -> p.getType().equals(type)).toList();
    }

    public static List<PurposeCode> allowed(int allowed) {
        return Arrays.stream(values()).filter(p -> p.getAllowedFrom().contains(allowed)).toList();
    }

    public static final class CpcOption {
        private final String code;
        private final PurposeCode purposeCode;
        private final Integer allowedFrom;

        public CpcOption(String code, PurposeCode purposeCode, Integer allowedFrom) {
            this.code = code;
            this.purposeCode = purposeCode;
            this.allowedFrom = allowedFrom;
        }

        public String getCode() {
            return code;
        }

        public PurposeCode getPurposeCode() {
            return purposeCode;
        }

        public Integer getAllowedFrom() {
            return allowedFrom;
        }

        public String getShortLabel() {
            return code;
        }

        public String getLongLabel() {
            return code + " - " + purposeCode.value();
        }
    }

    public static List<CpcOption> allCpcOptions() {
        return buildCpcOptions(Arrays.asList(values()));
    }

    public static List<CpcOption> cpcOptionsForType(ProcessType processType) {
        if (processType == null) {
            return allCpcOptions();
        }
        return buildCpcOptions(forType(processType));
    }

    private static List<CpcOption> buildCpcOptions(List<PurposeCode> purposeCodes) {
        List<CpcOption> result = new ArrayList<>();

        for (PurposeCode pc : purposeCodes) {
            List<Integer> allowedFromList = pc.getAllowedFrom();

            if (allowedFromList == null || allowedFromList.isEmpty()) {
                continue;
            }

            for (Integer allowed : allowedFromList) {
                String code = buildExpandedCpcCode(pc, allowed);
                result.add(new CpcOption(code, pc, allowed));
            }
        }

        result.sort(Comparator.comparing(CpcOption::getCode));
        return result;
    }

    private static String buildExpandedCpcCode(PurposeCode pc, Integer allowedFrom) {
        return pc.getSarsCode()
                + String.format("%02d", pc.getCpcCode())
                + String.format("%02d", allowedFrom);
    }

    public String getCpcCodePrefix() {
        return String.format("%02d", cpcCode);
    }

    public String buildExpandedCpcCode(String suffix) {
        return sarsCode + getCpcCodePrefix() + suffix;
    }

    public static String resolveCpcCode(String rawValue) {
        if (rawValue == null || rawValue.isBlank()) {
            return rawValue;
        }

        String value = rawValue.trim().toUpperCase();

        if (Character.isLetter(value.charAt(0))) {
            return value;
        }

        if (!value.matches("\\d+")) {
            return value;
        }

        if (value.length() != 2 && value.length() != 4) {
            return value;
        }

        String prefix = value.substring(0, 2);
        String suffix = value.length() == 4 ? value.substring(2) : "00";

        PurposeCode purposeCode = findByCpcPrefix(prefix);
        if (purposeCode == null) {
            return value;
        }

        return purposeCode.buildExpandedCpcCode(suffix);
    }

    public static PurposeCode findByCpcPrefix(String prefix) {
        if (prefix == null || prefix.isBlank()) {
            return null;
        }

        String normalized = prefix.trim();

        for (PurposeCode purposeCode : PurposeCode.values()) {
            if (purposeCode.getCpcCodePrefix().equals(normalized)) {
                return purposeCode;
            }
        }

        return null;
    }

}