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;
}
}