-
-
Save jwgmeligmeyling/b1e700b765600a67c77349fba9b12047 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.pallasathenagroup.entities; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
import com.fasterxml.jackson.annotation.JsonProperty; | |
import com.fasterxml.jackson.annotation.JsonProperty.Access; | |
import com.fasterxml.jackson.annotation.JsonView; | |
import com.pallasathenagroup.entities.hermes.OrderEntry; | |
import com.pallasathenagroup.entities.hermes.ShoppingBasketEntry; | |
import lombok.*; | |
import org.hibernate.annotations.Formula; | |
import javax.persistence.Column; | |
import javax.persistence.Entity; | |
import javax.persistence.FetchType; | |
import javax.persistence.GeneratedValue; | |
import javax.persistence.GenerationType; | |
import javax.persistence.Id; | |
import javax.persistence.Inheritance; | |
import javax.persistence.InheritanceType; | |
import javax.persistence.OneToMany; | |
import javax.persistence.Table; | |
import javax.validation.constraints.NotNull; | |
import java.math.BigDecimal; | |
import java.time.LocalDateTime; | |
import java.util.Comparator; | |
import java.util.List; | |
import java.util.Set; | |
/** | |
* @author Jan-Willem Gmelig Meyling | |
*/ | |
@Getter | |
@Setter | |
@Entity | |
@Table(name = "discount_code") | |
@ToString(of = {"id"}) | |
@Inheritance(strategy = InheritanceType.JOINED) | |
public abstract class DiscountCode extends IdIdentifiable<Long> implements Comparable<DiscountCode> { | |
public static final Comparator<DiscountCode> BY_DISCOUNT_ASC = Comparator.comparing(DiscountCode::getDiscountPercentage) | |
.thenComparingLong(DiscountCode::getId); | |
public static final Comparator<DiscountCode> BY_DISCOUNT_DESC = BY_DISCOUNT_ASC.reversed(); | |
@Id | |
@Column(name = "discount_code_id") | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
private Long id; | |
@NotNull | |
@Column(name = "discount_percentage", nullable = false, scale = 2) | |
private BigDecimal discountPercentage; | |
@JsonIgnore | |
@OneToMany(mappedBy = "discountCode", fetch = FetchType.LAZY) | |
private List<OrderEntry> applications; | |
/** | |
* Read-only property that contains the number of applications of this discount code. | |
* This only counts the applications where the payment is positive (either paid or | |
* likely to be paid, at least not timed-out, cancelled or refunded). This query | |
* also keeps in mind cases where there was no payment because a 100% discount code | |
* was used, e.g. the paid price for the entry was 0. | |
* | |
* @see com.pallasathenagroup.entities.hermes.PaymentStatusInfo#isPositive() | |
*/ | |
@Setter(AccessLevel.NONE) | |
@JsonView(Views.Internal.class) | |
@Formula("(SELECT COALESCE(SUM(oe.quantity), 0) FROM order_entry oe " + | |
"LEFT JOIN paynl_transaction tx ON tx.order_id = oe.order_id " + | |
"WHERE (tx.state > 0 OR oe.paid_price = 0) AND oe.discount_code_id = discount_code_id)") | |
@JsonProperty(value = "numberOfApplications", access = Access.READ_ONLY) | |
private int numberOfApplications; | |
@JsonIgnore | |
public abstract int getRemainingApplications(); | |
@JsonIgnore | |
public abstract LocalDateTime getExpires(); | |
@JsonIgnore | |
public abstract Set<Scope<?, ?>> getApplicableScopes(); | |
@Deprecated | |
public boolean applicableFor(ShoppingBasketEntry shoppingBasketEntry) { | |
return applicableFor(shoppingBasketEntry.getProduct()); | |
} | |
public boolean applicableFor(Product<?,?> product) { | |
return product.getScope().traverseToParents() | |
.anyMatch(getApplicableScopes()::contains); | |
} | |
@JsonIgnore | |
public boolean hasExpired() { | |
return getExpires().isBefore(LocalDateTime.now()); | |
} | |
@JsonIgnore | |
public boolean isActive() { | |
return !hasExpired(); | |
} | |
@Override | |
public int compareTo(DiscountCode o) { | |
return BY_DISCOUNT_ASC.compare(this, o); | |
} | |
@JsonIgnore | |
public boolean notExpired() { | |
return !hasExpired(); | |
} | |
public boolean hasHigherDiscountPercentageThan(DiscountCode discountCode) { | |
return getDiscountPercentage().compareTo(discountCode.getDiscountPercentage()) >= 0; | |
} | |
@JsonIgnore | |
public abstract String getDescription(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.pallasathenagroup.entities; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.annotation.JsonView; | |
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | |
import com.pallasathenagroup.entities.hermes.Order; | |
import com.pallasathenagroup.entities.hermes.OrderEntry; | |
import com.pallasathenagroup.entities.hermes.ShoppingBasket; | |
import lombok.*; | |
import org.hibernate.annotations.Fetch; | |
import org.hibernate.annotations.FetchMode; | |
import org.hibernate.annotations.Type; | |
import javax.persistence.CascadeType; | |
import javax.persistence.Column; | |
import javax.persistence.Entity; | |
import javax.persistence.FetchType; | |
import javax.persistence.JoinColumn; | |
import javax.persistence.JoinTable; | |
import javax.persistence.ManyToMany; | |
import javax.persistence.OneToMany; | |
import javax.persistence.OneToOne; | |
import javax.persistence.Table; | |
import javax.validation.constraints.NotNull; | |
import java.math.BigDecimal; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.Set; | |
/** | |
* A {@code GlobalPartner} is a {@link Partner} that is connected to one or multiple {@link Scope Scopes}. | |
* These partners are usually student associations, that collect revenue for marketing efforts for a specific | |
* faculty or study track. | |
* | |
* The global partner system utilizes the allowedIps and the partner forward function. | |
* | |
* Global partners share a many-to-many relations to scope, because student associations are not | |
* study specific. | |
* | |
* @author Jan-Willem Gmelig Meyling | |
*/ | |
@Getter | |
@Setter | |
@Entity | |
@JsonDeserialize | |
@Table(name = "global_partner") | |
@ToString(exclude = {"scopes", "orderEntries"}, callSuper = true) | |
public class GlobalPartner extends Partner { | |
@JsonIgnore | |
@JoinTable(name = "global_partner_scopes", joinColumns = { | |
@JoinColumn(name = "partner_id", referencedColumnName = "partner_id", nullable = false) | |
}, inverseJoinColumns = { | |
@JoinColumn(name = "scope_id", referencedColumnName = "scope_id", nullable = false) | |
}) | |
@Fetch(FetchMode.SUBSELECT) | |
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) | |
private Set<Scope<?, ?>> scopes; | |
@Type(type = "StringJsonObject") | |
@JsonView(Views.Internal.class) | |
@Column(name="allowed_ips", columnDefinition = "json") | |
private Collection<String> allowedIps; | |
@NotNull | |
@JsonIgnore | |
@OneToOne(optional = false, cascade = CascadeType.ALL, orphanRemoval = true) | |
@JoinColumn(name = "discount_code_id", referencedColumnName = "discount_code_id", nullable = false) | |
private PartnerDiscountCode discountCode; | |
@JsonIgnore | |
@OneToMany(mappedBy = "globalPartner", fetch = FetchType.LAZY) | |
private List<OrderEntry> orderEntries; | |
@JsonView(Views.Public.class) | |
public BigDecimal getDiscountPercentage() { | |
return getDiscountCode().getDiscountPercentage(); | |
} | |
@JsonView(Views.Public.class) | |
public void setDiscountPercentage(BigDecimal value) { | |
if (discountCode == null) { | |
discountCode = new PartnerDiscountCode(); | |
discountCode.setPartner(this); | |
} | |
discountCode.setDiscountPercentage(value); | |
} | |
@Override | |
public boolean isGlobal() { | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.pallasathenagroup.entities; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
import com.fasterxml.jackson.annotation.JsonProperty; | |
import com.fasterxml.jackson.annotation.JsonProperty.Access; | |
import com.fasterxml.jackson.annotation.JsonView; | |
import com.fasterxml.jackson.core.JsonParser; | |
import com.fasterxml.jackson.databind.DeserializationContext; | |
import com.fasterxml.jackson.databind.JsonNode; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | |
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; | |
import com.fasterxml.jackson.databind.node.ObjectNode; | |
import com.pallasathenagroup.entities.hermes.OrderEntry; | |
import com.pallasathenagroup.types.JsonType; | |
import lombok.Getter; | |
import lombok.Setter; | |
import org.hibernate.annotations.Type; | |
import org.hibernate.annotations.TypeDef; | |
import org.hibernate.annotations.TypeDefs; | |
import org.hibernate.validator.constraints.Length; | |
import org.hibernate.validator.constraints.NotBlank; | |
import javax.persistence.*; | |
import javax.validation.constraints.Pattern; | |
import java.io.IOException; | |
import java.math.BigDecimal; | |
import java.time.LocalDateTime; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
/** | |
* @author Jan-Willem Gmelig Meyling | |
*/ | |
@Getter | |
@Setter | |
@Entity | |
@Table(name = "partner") | |
@JsonIgnoreProperties(ignoreUnknown = true) | |
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) | |
@JsonDeserialize(using = Partner.PartnerDeserializer.class) | |
@TypeDefs({@TypeDef( name= "StringJsonObject", typeClass = JsonType.class)}) | |
public abstract class Partner extends IdIdentifiable<Long> { | |
@Id | |
@Column(name = "partner_id") | |
@JsonView(Views.Public.class) | |
@GeneratedValue(strategy = GenerationType.SEQUENCE) | |
private Long id; | |
@Column(name = "expires") | |
@JsonView(Views.Internal.class) | |
private LocalDateTime expires; | |
@JsonView(Views.Public.class) | |
@Column(name = "name", unique = true) | |
private String name; | |
@JsonView(Views.Internal.class) | |
@Column(name = "revenue_percentage", scale = 2) | |
private BigDecimal revenuePercentage; | |
@NotBlank | |
@Length(min = 2, max = 40) | |
@Pattern(regexp = "^[a-zA-Z0-9\\-]{2,40}$", | |
message = "Only a-z A-Z 0-9 and the dash (-) are allowed characters in a slug.") | |
@JsonView(Views.Public.class) | |
@Column(name = "slug", unique = true) | |
private String slug; | |
@Type(type = "StringJsonObject") | |
@JsonView(Views.Public.class) | |
@Column(name="visuals", columnDefinition = "json") | |
private Map<String, String> visuals; | |
@JsonProperty(value = "global", access = Access.READ_ONLY) | |
public abstract boolean isGlobal(); | |
public abstract Set<Scope<?,?>> getScopes(); | |
public abstract void setScopes(Set<Scope<?,?>> scopes); | |
static class PartnerDeserializer extends StdDeserializer<Partner> { | |
PartnerDeserializer() { | |
super(Partner.class); | |
} | |
@Override | |
public Partner deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { | |
ObjectMapper mapper = (ObjectMapper) p.getCodec(); | |
ObjectNode root = mapper.readTree(p); | |
JsonNode discountPercentage = root.get("discountPercentage"); | |
JsonNode typeNode = root.get("global"); | |
// No discountPercentage field, or field value null? -> GlobalPartner | |
boolean global = typeNode == null && discountPercentage != null && !discountPercentage.isNull(); | |
// Global field defined and true? -> Global partner | |
global |= typeNode != null && typeNode.booleanValue(); | |
// Convert JSON string to map... why | |
JsonNode visuals = root.get("visuals"); | |
if (visuals != null && visuals.isTextual()) { | |
root.set("visuals", mapper.readTree(visuals.asText())); | |
} | |
Class<? extends Partner> clasz = global ? GlobalPartner.class : ScopePartner.class; | |
JsonParser traverse = root.traverse(); | |
traverse.setCodec(mapper); | |
return mapper.readValue(traverse, clasz); | |
} | |
} | |
@JsonIgnore | |
public abstract List<OrderEntry> getOrderEntries(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.pallasathenagroup.entities; | |
import lombok.Getter; | |
import lombok.Setter; | |
import lombok.ToString; | |
import javax.persistence.*; | |
import java.time.LocalDateTime; | |
import java.util.Locale; | |
import java.util.ResourceBundle; | |
import java.util.Set; | |
/** | |
* @author Jan-Willem Gmelig Meyling | |
*/ | |
@Getter | |
@Setter | |
@Entity | |
@Table(name = "partner_discount_code") | |
@ToString(of = {}, callSuper = true) | |
@Inheritance(strategy = InheritanceType.JOINED) | |
public class PartnerDiscountCode extends DiscountCode { | |
@OneToOne(mappedBy = "discountCode", optional = false) | |
private GlobalPartner partner; | |
@Override | |
public int getRemainingApplications() { | |
return Integer.MAX_VALUE; | |
} | |
@Override | |
public LocalDateTime getExpires() { | |
return getPartner().getExpires(); | |
} | |
@Override | |
public Set<Scope<?, ?>> getApplicableScopes() { | |
return getPartner().getScopes(); | |
} | |
@Override | |
public String getDescription() { | |
Locale locale = getPartner().getScopes().stream() | |
.map(Scope::getInheritedLocale) | |
.findFirst() | |
.orElse(Locale.ENGLISH); | |
ResourceBundle resourceBundle = ResourceBundle.getBundle("com.pallasathenagroup.i18n", locale); | |
return resourceBundle.getString("partner-discount") + " " + getPartner().getName(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.pallasathenagroup.entities; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | |
import com.pallasathenagroup.entities.hermes.OrderEntry; | |
import lombok.*; | |
import org.hibernate.annotations.Fetch; | |
import org.hibernate.annotations.FetchMode; | |
import javax.persistence.CascadeType; | |
import javax.persistence.Entity; | |
import javax.persistence.FetchType; | |
import javax.persistence.OneToMany; | |
import javax.persistence.Table; | |
import java.util.List; | |
import java.util.Set; | |
/** | |
* A {@code ScopePartner} is a {@link Partner} that is connected to one or multiple {@link Scope} Scopes. | |
* These partners are usually study associations, that collect revenue for marketing efforts for a specific | |
* faculty or study track. | |
* | |
* Implementors must ensure that a parent {@link Scope} does not get bound to a {@code ScopePartner} when | |
* one of its {@link Scope#getSubScopes() subscopes} is already bound to a {@code ScopePartner}. | |
* | |
* @author Jan-Willem Gmelig Meyling | |
*/ | |
@Getter | |
@Setter | |
@Entity | |
@JsonDeserialize | |
@Table(name = "scope_partner") | |
@ToString(exclude = {"scopes", "orderEntries"}, callSuper = true) | |
public class ScopePartner extends Partner { | |
@JsonIgnore | |
@Fetch(FetchMode.SUBSELECT) | |
@OneToMany(mappedBy = "partner", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) | |
private Set<Scope<?, ?>> scopes; | |
@JsonIgnore | |
@OneToMany(mappedBy = "scopePartner", fetch = FetchType.LAZY) | |
private List<OrderEntry> orderEntries; | |
@Override | |
public void setScopes(Set<Scope<?, ?>> scopes) { | |
if (this.scopes != null) { | |
this.scopes.forEach(scope -> scope.setPartner(null)); | |
} | |
this.scopes = scopes; | |
this.scopes.forEach(scope -> scope.setPartner(this)); | |
} | |
@Override | |
public boolean isGlobal() { | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment