Consolidates models.py into a directory module.
This commit is contained in:
		
							parent
							
								
									278ca23c29
								
							
						
					
					
						commit
						875f736d67
					
				
					 24 changed files with 1193 additions and 1040 deletions
				
			
		|  | @ -4,7 +4,8 @@ from django.utils.translation import ugettext_lazy as _ | ||||||
| 
 | 
 | ||||||
| import nested_admin | import nested_admin | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EffectsDisplayMixin(object): | class EffectsDisplayMixin(object): | ||||||
|  | @ -15,12 +16,12 @@ class EffectsDisplayMixin(object): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ProductInline(admin.TabularInline): | class ProductInline(admin.TabularInline): | ||||||
|     model = rego.Product |     model = inventory.Product | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(rego.Category) | @admin.register(inventory.Category) | ||||||
| class CategoryAdmin(admin.ModelAdmin): | class CategoryAdmin(admin.ModelAdmin): | ||||||
|     model = rego.Category |     model = inventory.Category | ||||||
|     fields = ("name", "description", "required", "render_type", |     fields = ("name", "description", "required", "render_type", | ||||||
|               "limit_per_user", "order",) |               "limit_per_user", "order",) | ||||||
|     list_display = ("name", "description") |     list_display = ("name", "description") | ||||||
|  | @ -29,9 +30,9 @@ class CategoryAdmin(admin.ModelAdmin): | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(rego.Product) | @admin.register(inventory.Product) | ||||||
| class ProductAdmin(admin.ModelAdmin): | class ProductAdmin(admin.ModelAdmin): | ||||||
|     model = rego.Product |     model = inventory.Product | ||||||
|     list_display = ("name", "category", "description") |     list_display = ("name", "category", "description") | ||||||
|     list_filter = ("category", ) |     list_filter = ("category", ) | ||||||
| 
 | 
 | ||||||
|  | @ -39,18 +40,18 @@ class ProductAdmin(admin.ModelAdmin): | ||||||
| # Discounts | # Discounts | ||||||
| 
 | 
 | ||||||
| class DiscountForProductInline(admin.TabularInline): | class DiscountForProductInline(admin.TabularInline): | ||||||
|     model = rego.DiscountForProduct |     model = conditions.DiscountForProduct | ||||||
|     verbose_name = _("Product included in discount") |     verbose_name = _("Product included in discount") | ||||||
|     verbose_name_plural = _("Products included in discount") |     verbose_name_plural = _("Products included in discount") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DiscountForCategoryInline(admin.TabularInline): | class DiscountForCategoryInline(admin.TabularInline): | ||||||
|     model = rego.DiscountForCategory |     model = conditions.DiscountForCategory | ||||||
|     verbose_name = _("Category included in discount") |     verbose_name = _("Category included in discount") | ||||||
|     verbose_name_plural = _("Categories included in discount") |     verbose_name_plural = _("Categories included in discount") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(rego.TimeOrStockLimitDiscount) | @admin.register(conditions.TimeOrStockLimitDiscount) | ||||||
| class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): | class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): | ||||||
|     list_display = ( |     list_display = ( | ||||||
|         "description", |         "description", | ||||||
|  | @ -67,7 +68,7 @@ class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(rego.IncludedProductDiscount) | @admin.register(conditions.IncludedProductDiscount) | ||||||
| class IncludedProductDiscountAdmin(admin.ModelAdmin): | class IncludedProductDiscountAdmin(admin.ModelAdmin): | ||||||
| 
 | 
 | ||||||
|     def enablers(self, obj): |     def enablers(self, obj): | ||||||
|  | @ -87,7 +88,7 @@ class IncludedProductDiscountAdmin(admin.ModelAdmin): | ||||||
| # Vouchers | # Vouchers | ||||||
| 
 | 
 | ||||||
| class VoucherDiscountInline(nested_admin.NestedStackedInline): | class VoucherDiscountInline(nested_admin.NestedStackedInline): | ||||||
|     model = rego.VoucherDiscount |     model = conditions.VoucherDiscount | ||||||
|     verbose_name = _("Discount") |     verbose_name = _("Discount") | ||||||
| 
 | 
 | ||||||
|     # TODO work out why we're allowed to add more than one? |     # TODO work out why we're allowed to add more than one? | ||||||
|  | @ -100,7 +101,7 @@ class VoucherDiscountInline(nested_admin.NestedStackedInline): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class VoucherFlagInline(nested_admin.NestedStackedInline): | class VoucherFlagInline(nested_admin.NestedStackedInline): | ||||||
|     model = rego.VoucherFlag |     model = conditions.VoucherFlag | ||||||
|     verbose_name = _("Product and category enabled by voucher") |     verbose_name = _("Product and category enabled by voucher") | ||||||
|     verbose_name_plural = _("Products and categories enabled by voucher") |     verbose_name_plural = _("Products and categories enabled by voucher") | ||||||
| 
 | 
 | ||||||
|  | @ -109,7 +110,7 @@ class VoucherFlagInline(nested_admin.NestedStackedInline): | ||||||
|     extra = 1 |     extra = 1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(rego.Voucher) | @admin.register(inventory.Voucher) | ||||||
| class VoucherAdmin(nested_admin.NestedAdmin): | class VoucherAdmin(nested_admin.NestedAdmin): | ||||||
| 
 | 
 | ||||||
|     def effects(self, obj): |     def effects(self, obj): | ||||||
|  | @ -133,7 +134,7 @@ class VoucherAdmin(nested_admin.NestedAdmin): | ||||||
| 
 | 
 | ||||||
|         return "\n".join(out) |         return "\n".join(out) | ||||||
| 
 | 
 | ||||||
|     model = rego.Voucher |     model = inventory.Voucher | ||||||
|     list_display = ("recipient", "code", "effects") |     list_display = ("recipient", "code", "effects") | ||||||
|     inlines = [ |     inlines = [ | ||||||
|         VoucherDiscountInline, |         VoucherDiscountInline, | ||||||
|  | @ -142,7 +143,7 @@ class VoucherAdmin(nested_admin.NestedAdmin): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Enabling conditions | # Enabling conditions | ||||||
| @admin.register(rego.ProductFlag) | @admin.register(conditions.ProductFlag) | ||||||
| class ProductFlagAdmin( | class ProductFlagAdmin( | ||||||
|         nested_admin.NestedAdmin, |         nested_admin.NestedAdmin, | ||||||
|         EffectsDisplayMixin): |         EffectsDisplayMixin): | ||||||
|  | @ -150,7 +151,7 @@ class ProductFlagAdmin( | ||||||
|     def enablers(self, obj): |     def enablers(self, obj): | ||||||
|         return list(obj.enabling_products.all()) |         return list(obj.enabling_products.all()) | ||||||
| 
 | 
 | ||||||
|     model = rego.ProductFlag |     model = conditions.ProductFlag | ||||||
|     fields = ("description", "enabling_products", "condition", "products", |     fields = ("description", "enabling_products", "condition", "products", | ||||||
|               "categories"), |               "categories"), | ||||||
| 
 | 
 | ||||||
|  | @ -158,12 +159,12 @@ class ProductFlagAdmin( | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Enabling conditions | # Enabling conditions | ||||||
| @admin.register(rego.CategoryFlag) | @admin.register(conditions.CategoryFlag) | ||||||
| class CategoryFlagAdmin( | class CategoryFlagAdmin( | ||||||
|         nested_admin.NestedAdmin, |         nested_admin.NestedAdmin, | ||||||
|         EffectsDisplayMixin): |         EffectsDisplayMixin): | ||||||
| 
 | 
 | ||||||
|     model = rego.CategoryFlag |     model = conditions.CategoryFlag | ||||||
|     fields = ("description", "enabling_category", "condition", "products", |     fields = ("description", "enabling_category", "condition", "products", | ||||||
|               "categories"), |               "categories"), | ||||||
| 
 | 
 | ||||||
|  | @ -172,11 +173,11 @@ class CategoryFlagAdmin( | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Enabling conditions | # Enabling conditions | ||||||
| @admin.register(rego.TimeOrStockLimitFlag) | @admin.register(conditions.TimeOrStockLimitFlag) | ||||||
| class TimeOrStockLimitFlagAdmin( | class TimeOrStockLimitFlagAdmin( | ||||||
|         nested_admin.NestedAdmin, |         nested_admin.NestedAdmin, | ||||||
|         EffectsDisplayMixin): |         EffectsDisplayMixin): | ||||||
|     model = rego.TimeOrStockLimitFlag |     model = conditions.TimeOrStockLimitFlag | ||||||
| 
 | 
 | ||||||
|     list_display = ( |     list_display = ( | ||||||
|         "description", |         "description", | ||||||
|  |  | ||||||
|  | @ -9,8 +9,10 @@ from django.db import transaction | ||||||
| from django.db.models import Max | from django.db.models import Max | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego |  | ||||||
| from registrasion.exceptions import CartValidationError | from registrasion.exceptions import CartValidationError | ||||||
|  | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| from category import CategoryController | from category import CategoryController | ||||||
| from conditions import ConditionController | from conditions import ConditionController | ||||||
|  | @ -28,9 +30,9 @@ class CartController(object): | ||||||
|         if there isn't one ready yet. ''' |         if there isn't one ready yet. ''' | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             existing = rego.Cart.objects.get(user=user, active=True) |             existing = commerce.Cart.objects.get(user=user, active=True) | ||||||
|         except ObjectDoesNotExist: |         except ObjectDoesNotExist: | ||||||
|             existing = rego.Cart.objects.create( |             existing = commerce.Cart.objects.create( | ||||||
|                 user=user, |                 user=user, | ||||||
|                 time_last_updated=timezone.now(), |                 time_last_updated=timezone.now(), | ||||||
|                 reservation_duration=datetime.timedelta(), |                 reservation_duration=datetime.timedelta(), | ||||||
|  | @ -47,10 +49,10 @@ class CartController(object): | ||||||
| 
 | 
 | ||||||
|         # If we have vouchers, we're entitled to an hour at minimum. |         # If we have vouchers, we're entitled to an hour at minimum. | ||||||
|         if len(self.cart.vouchers.all()) >= 1: |         if len(self.cart.vouchers.all()) >= 1: | ||||||
|             reservations.append(rego.Voucher.RESERVATION_DURATION) |             reservations.append(inventory.Voucher.RESERVATION_DURATION) | ||||||
| 
 | 
 | ||||||
|         # Else, it's the maximum of the included products |         # Else, it's the maximum of the included products | ||||||
|         items = rego.ProductItem.objects.filter(cart=self.cart) |         items = commerce.ProductItem.objects.filter(cart=self.cart) | ||||||
|         agg = items.aggregate(Max("product__reservation_duration")) |         agg = items.aggregate(Max("product__reservation_duration")) | ||||||
|         product_max = agg["product__reservation_duration__max"] |         product_max = agg["product__reservation_duration__max"] | ||||||
| 
 | 
 | ||||||
|  | @ -79,7 +81,7 @@ class CartController(object): | ||||||
|         is violated. `product_quantities` is an iterable of (product, quantity) |         is violated. `product_quantities` is an iterable of (product, quantity) | ||||||
|         pairs. ''' |         pairs. ''' | ||||||
| 
 | 
 | ||||||
|         items_in_cart = rego.ProductItem.objects.filter(cart=self.cart) |         items_in_cart = commerce.ProductItem.objects.filter(cart=self.cart) | ||||||
|         items_in_cart = items_in_cart.select_related( |         items_in_cart = items_in_cart.select_related( | ||||||
|             "product", |             "product", | ||||||
|             "product__category", |             "product__category", | ||||||
|  | @ -99,14 +101,14 @@ class CartController(object): | ||||||
| 
 | 
 | ||||||
|         for product, quantity in product_quantities: |         for product, quantity in product_quantities: | ||||||
|             try: |             try: | ||||||
|                 product_item = rego.ProductItem.objects.get( |                 product_item = commerce.ProductItem.objects.get( | ||||||
|                     cart=self.cart, |                     cart=self.cart, | ||||||
|                     product=product, |                     product=product, | ||||||
|                 ) |                 ) | ||||||
|                 product_item.quantity = quantity |                 product_item.quantity = quantity | ||||||
|                 product_item.save() |                 product_item.save() | ||||||
|             except ObjectDoesNotExist: |             except ObjectDoesNotExist: | ||||||
|                 rego.ProductItem.objects.create( |                 commerce.ProductItem.objects.create( | ||||||
|                     cart=self.cart, |                     cart=self.cart, | ||||||
|                     product=product, |                     product=product, | ||||||
|                     quantity=quantity, |                     quantity=quantity, | ||||||
|  | @ -176,7 +178,7 @@ class CartController(object): | ||||||
|         ''' Applies the voucher with the given code to this cart. ''' |         ''' Applies the voucher with the given code to this cart. ''' | ||||||
| 
 | 
 | ||||||
|         # Try and find the voucher |         # Try and find the voucher | ||||||
|         voucher = rego.Voucher.objects.get(code=voucher_code.upper()) |         voucher = inventory.Voucher.objects.get(code=voucher_code.upper()) | ||||||
| 
 | 
 | ||||||
|         # Re-applying vouchers should be idempotent |         # Re-applying vouchers should be idempotent | ||||||
|         if voucher in self.cart.vouchers.all(): |         if voucher in self.cart.vouchers.all(): | ||||||
|  | @ -193,7 +195,7 @@ class CartController(object): | ||||||
|         Raises ValidationError if not. ''' |         Raises ValidationError if not. ''' | ||||||
| 
 | 
 | ||||||
|         # Is voucher exhausted? |         # Is voucher exhausted? | ||||||
|         active_carts = rego.Cart.reserved_carts() |         active_carts = commerce.Cart.reserved_carts() | ||||||
| 
 | 
 | ||||||
|         # It's invalid for a user to enter a voucher that's exhausted |         # It's invalid for a user to enter a voucher that's exhausted | ||||||
|         carts_with_voucher = active_carts.filter(vouchers=voucher) |         carts_with_voucher = active_carts.filter(vouchers=voucher) | ||||||
|  | @ -238,7 +240,7 @@ class CartController(object): | ||||||
|         except ValidationError as ve: |         except ValidationError as ve: | ||||||
|             errors.append(ve) |             errors.append(ve) | ||||||
| 
 | 
 | ||||||
|         items = rego.ProductItem.objects.filter(cart=cart) |         items = commerce.ProductItem.objects.filter(cart=cart) | ||||||
| 
 | 
 | ||||||
|         product_quantities = list((i.product, i.quantity) for i in items) |         product_quantities = list((i.product, i.quantity) for i in items) | ||||||
|         try: |         try: | ||||||
|  | @ -248,7 +250,7 @@ class CartController(object): | ||||||
|                 errors.append(error.message[1]) |                 errors.append(error.message[1]) | ||||||
| 
 | 
 | ||||||
|         # Validate the discounts |         # Validate the discounts | ||||||
|         discount_items = rego.DiscountItem.objects.filter(cart=cart) |         discount_items = commerce.DiscountItem.objects.filter(cart=cart) | ||||||
|         seen_discounts = set() |         seen_discounts = set() | ||||||
| 
 | 
 | ||||||
|         for discount_item in discount_items: |         for discount_item in discount_items: | ||||||
|  | @ -256,7 +258,7 @@ class CartController(object): | ||||||
|             if discount in seen_discounts: |             if discount in seen_discounts: | ||||||
|                 continue |                 continue | ||||||
|             seen_discounts.add(discount) |             seen_discounts.add(discount) | ||||||
|             real_discount = rego.DiscountBase.objects.get_subclass( |             real_discount = conditions.DiscountBase.objects.get_subclass( | ||||||
|                 pk=discount.pk) |                 pk=discount.pk) | ||||||
|             cond = ConditionController.for_condition(real_discount) |             cond = ConditionController.for_condition(real_discount) | ||||||
| 
 | 
 | ||||||
|  | @ -287,7 +289,7 @@ class CartController(object): | ||||||
|             self.cart.vouchers.remove(voucher) |             self.cart.vouchers.remove(voucher) | ||||||
| 
 | 
 | ||||||
|         # Fix products and discounts |         # Fix products and discounts | ||||||
|         items = rego.ProductItem.objects.filter(cart=self.cart) |         items = commerce.ProductItem.objects.filter(cart=self.cart) | ||||||
|         items = items.select_related("product") |         items = items.select_related("product") | ||||||
|         products = set(i.product for i in items) |         products = set(i.product for i in items) | ||||||
|         available = set(ProductController.available_products( |         available = set(ProductController.available_products( | ||||||
|  | @ -306,7 +308,7 @@ class CartController(object): | ||||||
|         ''' |         ''' | ||||||
| 
 | 
 | ||||||
|         # Delete the existing entries. |         # Delete the existing entries. | ||||||
|         rego.DiscountItem.objects.filter(cart=self.cart).delete() |         commerce.DiscountItem.objects.filter(cart=self.cart).delete() | ||||||
| 
 | 
 | ||||||
|         product_items = self.cart.productitem_set.all().select_related( |         product_items = self.cart.productitem_set.all().select_related( | ||||||
|             "product", "product__category", |             "product", "product__category", | ||||||
|  | @ -331,7 +333,7 @@ class CartController(object): | ||||||
|         def matches(discount): |         def matches(discount): | ||||||
|             ''' Returns True if and only if the given discount apples to |             ''' Returns True if and only if the given discount apples to | ||||||
|             our product. ''' |             our product. ''' | ||||||
|             if isinstance(discount.clause, rego.DiscountForCategory): |             if isinstance(discount.clause, conditions.DiscountForCategory): | ||||||
|                 return discount.clause.category == product.category |                 return discount.clause.category == product.category | ||||||
|             else: |             else: | ||||||
|                 return discount.clause.product == product |                 return discount.clause.product == product | ||||||
|  | @ -356,7 +358,7 @@ class CartController(object): | ||||||
| 
 | 
 | ||||||
|             # Get a provisional instance for this DiscountItem |             # Get a provisional instance for this DiscountItem | ||||||
|             # with the quantity set to as much as we have in the cart |             # with the quantity set to as much as we have in the cart | ||||||
|             discount_item = rego.DiscountItem.objects.create( |             discount_item = commerce.DiscountItem.objects.create( | ||||||
|                 product=product, |                 product=product, | ||||||
|                 cart=self.cart, |                 cart=self.cart, | ||||||
|                 discount=candidate.discount, |                 discount=candidate.discount, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| 
 | 
 | ||||||
|  | @ -22,7 +23,9 @@ class CategoryController(object): | ||||||
|         from product import ProductController |         from product import ProductController | ||||||
| 
 | 
 | ||||||
|         if products is AllProducts: |         if products is AllProducts: | ||||||
|             products = rego.Product.objects.all().select_related("category") |             products = inventory.Product.objects.all().select_related( | ||||||
|  |                 "category", | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|         available = ProductController.available_products( |         available = ProductController.available_products( | ||||||
|             user, |             user, | ||||||
|  | @ -41,13 +44,13 @@ class CategoryController(object): | ||||||
|             # We don't need to waste the following queries |             # We don't need to waste the following queries | ||||||
|             return 99999999 |             return 99999999 | ||||||
| 
 | 
 | ||||||
|         carts = rego.Cart.objects.filter( |         carts = commerce.Cart.objects.filter( | ||||||
|             user=user, |             user=user, | ||||||
|             active=False, |             active=False, | ||||||
|             released=False, |             released=False, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         items = rego.ProductItem.objects.filter( |         items = commerce.ProductItem.objects.filter( | ||||||
|             cart__in=carts, |             cart__in=carts, | ||||||
|             product__category=self.category, |             product__category=self.category, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,9 @@ from collections import namedtuple | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ConditionAndRemainder = namedtuple( | ConditionAndRemainder = namedtuple( | ||||||
|  | @ -29,15 +31,15 @@ class ConditionController(object): | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def for_condition(condition): |     def for_condition(condition): | ||||||
|         CONTROLLERS = { |         CONTROLLERS = { | ||||||
|             rego.CategoryFlag: CategoryConditionController, |             conditions.CategoryFlag: CategoryConditionController, | ||||||
|             rego.IncludedProductDiscount: ProductConditionController, |             conditions.IncludedProductDiscount: ProductConditionController, | ||||||
|             rego.ProductFlag: ProductConditionController, |             conditions.ProductFlag: ProductConditionController, | ||||||
|             rego.TimeOrStockLimitDiscount: |             conditions.TimeOrStockLimitDiscount: | ||||||
|                 TimeOrStockLimitDiscountController, |                 TimeOrStockLimitDiscountController, | ||||||
|             rego.TimeOrStockLimitFlag: |             conditions.TimeOrStockLimitFlag: | ||||||
|                 TimeOrStockLimitFlagController, |                 TimeOrStockLimitFlagController, | ||||||
|             rego.VoucherDiscount: VoucherConditionController, |             conditions.VoucherDiscount: VoucherConditionController, | ||||||
|             rego.VoucherFlag: VoucherConditionController, |             conditions.VoucherFlag: VoucherConditionController, | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|  | @ -121,7 +123,7 @@ class ConditionController(object): | ||||||
|             # Get all products covered by this condition, and the products |             # Get all products covered by this condition, and the products | ||||||
|             # from the categories covered by this condition |             # from the categories covered by this condition | ||||||
|             cond_products = condition.products.all() |             cond_products = condition.products.all() | ||||||
|             from_category = rego.Product.objects.filter( |             from_category = inventory.Product.objects.filter( | ||||||
|                 category__in=condition.categories.all(), |                 category__in=condition.categories.all(), | ||||||
|             ).all() |             ).all() | ||||||
|             all_products = cond_products | from_category |             all_products = cond_products | from_category | ||||||
|  | @ -199,11 +201,11 @@ class CategoryConditionController(ConditionController): | ||||||
|         ''' returns True if the user has a product from a category that invokes |         ''' returns True if the user has a product from a category that invokes | ||||||
|         this condition in one of their carts ''' |         this condition in one of their carts ''' | ||||||
| 
 | 
 | ||||||
|         carts = rego.Cart.objects.filter(user=user, released=False) |         carts = commerce.Cart.objects.filter(user=user, released=False) | ||||||
|         enabling_products = rego.Product.objects.filter( |         enabling_products = inventory.Product.objects.filter( | ||||||
|             category=self.condition.enabling_category, |             category=self.condition.enabling_category, | ||||||
|         ) |         ) | ||||||
|         products_count = rego.ProductItem.objects.filter( |         products_count = commerce.ProductItem.objects.filter( | ||||||
|             cart__in=carts, |             cart__in=carts, | ||||||
|             product__in=enabling_products, |             product__in=enabling_products, | ||||||
|         ).count() |         ).count() | ||||||
|  | @ -221,8 +223,8 @@ class ProductConditionController(ConditionController): | ||||||
|         ''' returns True if the user has a product that invokes this |         ''' returns True if the user has a product that invokes this | ||||||
|         condition in one of their carts ''' |         condition in one of their carts ''' | ||||||
| 
 | 
 | ||||||
|         carts = rego.Cart.objects.filter(user=user, released=False) |         carts = commerce.Cart.objects.filter(user=user, released=False) | ||||||
|         products_count = rego.ProductItem.objects.filter( |         products_count = commerce.ProductItem.objects.filter( | ||||||
|             cart__in=carts, |             cart__in=carts, | ||||||
|             product__in=self.condition.enabling_products.all(), |             product__in=self.condition.enabling_products.all(), | ||||||
|         ).count() |         ).count() | ||||||
|  | @ -267,7 +269,7 @@ class TimeOrStockLimitConditionController(ConditionController): | ||||||
|             return 99999999 |             return 99999999 | ||||||
| 
 | 
 | ||||||
|         # We care about all reserved carts, but not the user's current cart |         # We care about all reserved carts, but not the user's current cart | ||||||
|         reserved_carts = rego.Cart.reserved_carts() |         reserved_carts = commerce.Cart.reserved_carts() | ||||||
|         reserved_carts = reserved_carts.exclude( |         reserved_carts = reserved_carts.exclude( | ||||||
|             user=user, |             user=user, | ||||||
|             active=True, |             active=True, | ||||||
|  | @ -284,12 +286,12 @@ class TimeOrStockLimitFlagController( | ||||||
|         TimeOrStockLimitConditionController): |         TimeOrStockLimitConditionController): | ||||||
| 
 | 
 | ||||||
|     def _items(self): |     def _items(self): | ||||||
|         category_products = rego.Product.objects.filter( |         category_products = inventory.Product.objects.filter( | ||||||
|             category__in=self.ceiling.categories.all(), |             category__in=self.ceiling.categories.all(), | ||||||
|         ) |         ) | ||||||
|         products = self.ceiling.products.all() | category_products |         products = self.ceiling.products.all() | category_products | ||||||
| 
 | 
 | ||||||
|         product_items = rego.ProductItem.objects.filter( |         product_items = commerce.ProductItem.objects.filter( | ||||||
|             product__in=products.all(), |             product__in=products.all(), | ||||||
|         ) |         ) | ||||||
|         return product_items |         return product_items | ||||||
|  | @ -298,7 +300,7 @@ class TimeOrStockLimitFlagController( | ||||||
| class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController): | class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController): | ||||||
| 
 | 
 | ||||||
|     def _items(self): |     def _items(self): | ||||||
|         discount_items = rego.DiscountItem.objects.filter( |         discount_items = commerce.DiscountItem.objects.filter( | ||||||
|             discount=self.ceiling, |             discount=self.ceiling, | ||||||
|         ) |         ) | ||||||
|         return discount_items |         return discount_items | ||||||
|  | @ -312,7 +314,7 @@ class VoucherConditionController(ConditionController): | ||||||
| 
 | 
 | ||||||
|     def is_met(self, user): |     def is_met(self, user): | ||||||
|         ''' returns True if the user has the given voucher attached. ''' |         ''' returns True if the user has the given voucher attached. ''' | ||||||
|         carts_count = rego.Cart.objects.filter( |         carts_count = commerce.Cart.objects.filter( | ||||||
|             user=user, |             user=user, | ||||||
|             vouchers=self.condition.voucher, |             vouchers=self.condition.voucher, | ||||||
|         ).count() |         ).count() | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from django.db import transaction | from django.db import transaction | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CreditNoteController(object): | class CreditNoteController(object): | ||||||
|  | @ -14,7 +14,7 @@ class CreditNoteController(object): | ||||||
|         the given invoice. You need to call InvoiceController.update_status() |         the given invoice. You need to call InvoiceController.update_status() | ||||||
|         to set the status correctly, if appropriate. ''' |         to set the status correctly, if appropriate. ''' | ||||||
| 
 | 
 | ||||||
|         credit_note = rego.CreditNote.objects.create( |         credit_note = commerce.CreditNote.objects.create( | ||||||
|             invoice=invoice, |             invoice=invoice, | ||||||
|             amount=0-value,  # Credit notes start off as a payment against inv. |             amount=0-value,  # Credit notes start off as a payment against inv. | ||||||
|             reference="ONE MOMENT", |             reference="ONE MOMENT", | ||||||
|  | @ -39,7 +39,7 @@ class CreditNoteController(object): | ||||||
|         inv.validate_allowed_to_pay() |         inv.validate_allowed_to_pay() | ||||||
| 
 | 
 | ||||||
|         # Apply payment to invoice |         # Apply payment to invoice | ||||||
|         rego.CreditNoteApplication.objects.create( |         commerce.CreditNoteApplication.objects.create( | ||||||
|             parent=self.credit_note, |             parent=self.credit_note, | ||||||
|             invoice=invoice, |             invoice=invoice, | ||||||
|             amount=self.credit_note.value, |             amount=self.credit_note.value, | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import itertools | import itertools | ||||||
| 
 | 
 | ||||||
| from conditions import ConditionController | from conditions import ConditionController | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
| 
 | 
 | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| 
 | 
 | ||||||
|  | @ -24,15 +25,15 @@ def available_discounts(user, categories, products): | ||||||
|     not including products that are pending purchase. ''' |     not including products that are pending purchase. ''' | ||||||
| 
 | 
 | ||||||
|     # discounts that match provided categories |     # discounts that match provided categories | ||||||
|     category_discounts = rego.DiscountForCategory.objects.filter( |     category_discounts = conditions.DiscountForCategory.objects.filter( | ||||||
|         category__in=categories |         category__in=categories | ||||||
|     ) |     ) | ||||||
|     # discounts that match provided products |     # discounts that match provided products | ||||||
|     product_discounts = rego.DiscountForProduct.objects.filter( |     product_discounts = conditions.DiscountForProduct.objects.filter( | ||||||
|         product__in=products |         product__in=products | ||||||
|     ) |     ) | ||||||
|     # discounts that match categories for provided products |     # discounts that match categories for provided products | ||||||
|     product_category_discounts = rego.DiscountForCategory.objects.filter( |     product_category_discounts = conditions.DiscountForCategory.objects.filter( | ||||||
|         category__in=(product.category for product in products) |         category__in=(product.category for product in products) | ||||||
|     ) |     ) | ||||||
|     # (Not relevant: discounts that match products in provided categories) |     # (Not relevant: discounts that match products in provided categories) | ||||||
|  | @ -60,7 +61,7 @@ def available_discounts(user, categories, products): | ||||||
|     failed_discounts = set() |     failed_discounts = set() | ||||||
| 
 | 
 | ||||||
|     for discount in potential_discounts: |     for discount in potential_discounts: | ||||||
|         real_discount = rego.DiscountBase.objects.get_subclass( |         real_discount = conditions.DiscountBase.objects.get_subclass( | ||||||
|             pk=discount.discount.pk, |             pk=discount.discount.pk, | ||||||
|         ) |         ) | ||||||
|         cond = ConditionController.for_condition(real_discount) |         cond = ConditionController.for_condition(real_discount) | ||||||
|  | @ -68,7 +69,7 @@ def available_discounts(user, categories, products): | ||||||
|         # Count the past uses of the given discount item. |         # Count the past uses of the given discount item. | ||||||
|         # If this user has exceeded the limit for the clause, this clause |         # If this user has exceeded the limit for the clause, this clause | ||||||
|         # is not available any more. |         # is not available any more. | ||||||
|         past_uses = rego.DiscountItem.objects.filter( |         past_uses = commerce.DiscountItem.objects.filter( | ||||||
|             cart__user=user, |             cart__user=user, | ||||||
|             cart__active=False,  # Only past carts count |             cart__active=False,  # Only past carts count | ||||||
|             cart__released=False,  # You can reuse refunded discounts |             cart__released=False,  # You can reuse refunded discounts | ||||||
|  |  | ||||||
|  | @ -5,7 +5,9 @@ from django.db import transaction | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
|  | from registrasion.models import people | ||||||
| 
 | 
 | ||||||
| from cart import CartController | from cart import CartController | ||||||
| from credit_note import CreditNoteController | from credit_note import CreditNoteController | ||||||
|  | @ -25,8 +27,8 @@ class InvoiceController(object): | ||||||
|         an invoice is generated.''' |         an invoice is generated.''' | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             invoice = rego.Invoice.objects.exclude( |             invoice = commerce.Invoice.objects.exclude( | ||||||
|                 status=rego.Invoice.STATUS_VOID, |                 status=commerce.Invoice.STATUS_VOID, | ||||||
|             ).get( |             ).get( | ||||||
|                 cart=cart, |                 cart=cart, | ||||||
|                 cart_revision=cart.revision, |                 cart_revision=cart.revision, | ||||||
|  | @ -42,19 +44,19 @@ class InvoiceController(object): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def void_all_invoices(cls, cart): |     def void_all_invoices(cls, cart): | ||||||
|         invoices = rego.Invoice.objects.filter(cart=cart).all() |         invoices = commerce.Invoice.objects.filter(cart=cart).all() | ||||||
|         for invoice in invoices: |         for invoice in invoices: | ||||||
|             cls(invoice).void() |             cls(invoice).void() | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def resolve_discount_value(cls, item): |     def resolve_discount_value(cls, item): | ||||||
|         try: |         try: | ||||||
|             condition = rego.DiscountForProduct.objects.get( |             condition = conditions.DiscountForProduct.objects.get( | ||||||
|                 discount=item.discount, |                 discount=item.discount, | ||||||
|                 product=item.product |                 product=item.product | ||||||
|             ) |             ) | ||||||
|         except ObjectDoesNotExist: |         except ObjectDoesNotExist: | ||||||
|             condition = rego.DiscountForCategory.objects.get( |             condition = conditions.DiscountForCategory.objects.get( | ||||||
|                 discount=item.discount, |                 discount=item.discount, | ||||||
|                 category=item.product.category |                 category=item.product.category | ||||||
|             ) |             ) | ||||||
|  | @ -75,22 +77,22 @@ class InvoiceController(object): | ||||||
|         due = max(issued, reservation_limit) |         due = max(issued, reservation_limit) | ||||||
| 
 | 
 | ||||||
|         # Get the invoice recipient |         # Get the invoice recipient | ||||||
|         profile = rego.AttendeeProfileBase.objects.get_subclass( |         profile = people.AttendeeProfileBase.objects.get_subclass( | ||||||
|             id=cart.user.attendee.attendeeprofilebase.id, |             id=cart.user.attendee.attendeeprofilebase.id, | ||||||
|         ) |         ) | ||||||
|         recipient = profile.invoice_recipient() |         recipient = profile.invoice_recipient() | ||||||
|         invoice = rego.Invoice.objects.create( |         invoice = commerce.Invoice.objects.create( | ||||||
|             user=cart.user, |             user=cart.user, | ||||||
|             cart=cart, |             cart=cart, | ||||||
|             cart_revision=cart.revision, |             cart_revision=cart.revision, | ||||||
|             status=rego.Invoice.STATUS_UNPAID, |             status=commerce.Invoice.STATUS_UNPAID, | ||||||
|             value=Decimal(), |             value=Decimal(), | ||||||
|             issue_time=issued, |             issue_time=issued, | ||||||
|             due_time=due, |             due_time=due, | ||||||
|             recipient=recipient, |             recipient=recipient, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         product_items = rego.ProductItem.objects.filter(cart=cart) |         product_items = commerce.ProductItem.objects.filter(cart=cart) | ||||||
| 
 | 
 | ||||||
|         if len(product_items) == 0: |         if len(product_items) == 0: | ||||||
|             raise ValidationError("Your cart is empty.") |             raise ValidationError("Your cart is empty.") | ||||||
|  | @ -98,11 +100,11 @@ class InvoiceController(object): | ||||||
|         product_items = product_items.order_by( |         product_items = product_items.order_by( | ||||||
|             "product__category__order", "product__order" |             "product__category__order", "product__order" | ||||||
|         ) |         ) | ||||||
|         discount_items = rego.DiscountItem.objects.filter(cart=cart) |         discount_items = commerce.DiscountItem.objects.filter(cart=cart) | ||||||
|         invoice_value = Decimal() |         invoice_value = Decimal() | ||||||
|         for item in product_items: |         for item in product_items: | ||||||
|             product = item.product |             product = item.product | ||||||
|             line_item = rego.LineItem.objects.create( |             line_item = commerce.LineItem.objects.create( | ||||||
|                 invoice=invoice, |                 invoice=invoice, | ||||||
|                 description="%s - %s" % (product.category.name, product.name), |                 description="%s - %s" % (product.category.name, product.name), | ||||||
|                 quantity=item.quantity, |                 quantity=item.quantity, | ||||||
|  | @ -112,7 +114,7 @@ class InvoiceController(object): | ||||||
|             invoice_value += line_item.quantity * line_item.price |             invoice_value += line_item.quantity * line_item.price | ||||||
| 
 | 
 | ||||||
|         for item in discount_items: |         for item in discount_items: | ||||||
|             line_item = rego.LineItem.objects.create( |             line_item = commerce.LineItem.objects.create( | ||||||
|                 invoice=invoice, |                 invoice=invoice, | ||||||
|                 description=item.discount.description, |                 description=item.discount.description, | ||||||
|                 quantity=item.quantity, |                 quantity=item.quantity, | ||||||
|  | @ -170,7 +172,7 @@ class InvoiceController(object): | ||||||
|     def total_payments(self): |     def total_payments(self): | ||||||
|         ''' Returns the total amount paid towards this invoice. ''' |         ''' Returns the total amount paid towards this invoice. ''' | ||||||
| 
 | 
 | ||||||
|         payments = rego.PaymentBase.objects.filter(invoice=self.invoice) |         payments = commerce.PaymentBase.objects.filter(invoice=self.invoice) | ||||||
|         total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0 |         total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0 | ||||||
|         return total_paid |         return total_paid | ||||||
| 
 | 
 | ||||||
|  | @ -180,12 +182,12 @@ class InvoiceController(object): | ||||||
| 
 | 
 | ||||||
|         old_status = self.invoice.status |         old_status = self.invoice.status | ||||||
|         total_paid = self.total_payments() |         total_paid = self.total_payments() | ||||||
|         num_payments = rego.PaymentBase.objects.filter( |         num_payments = commerce.PaymentBase.objects.filter( | ||||||
|             invoice=self.invoice, |             invoice=self.invoice, | ||||||
|         ).count() |         ).count() | ||||||
|         remainder = self.invoice.value - total_paid |         remainder = self.invoice.value - total_paid | ||||||
| 
 | 
 | ||||||
|         if old_status == rego.Invoice.STATUS_UNPAID: |         if old_status == commerce.Invoice.STATUS_UNPAID: | ||||||
|             # Invoice had an amount owing |             # Invoice had an amount owing | ||||||
|             if remainder <= 0: |             if remainder <= 0: | ||||||
|                 # Invoice no longer has amount owing |                 # Invoice no longer has amount owing | ||||||
|  | @ -199,15 +201,15 @@ class InvoiceController(object): | ||||||
|             elif total_paid == 0 and num_payments > 0: |             elif total_paid == 0 and num_payments > 0: | ||||||
|                 # Invoice has multiple payments totalling zero |                 # Invoice has multiple payments totalling zero | ||||||
|                 self._mark_void() |                 self._mark_void() | ||||||
|         elif old_status == rego.Invoice.STATUS_PAID: |         elif old_status == commerce.Invoice.STATUS_PAID: | ||||||
|             if remainder > 0: |             if remainder > 0: | ||||||
|                 # Invoice went from having a remainder of zero or less |                 # Invoice went from having a remainder of zero or less | ||||||
|                 # to having a positive remainder -- must be a refund |                 # to having a positive remainder -- must be a refund | ||||||
|                 self._mark_refunded() |                 self._mark_refunded() | ||||||
|         elif old_status == rego.Invoice.STATUS_REFUNDED: |         elif old_status == commerce.Invoice.STATUS_REFUNDED: | ||||||
|             # Should not ever change from here |             # Should not ever change from here | ||||||
|             pass |             pass | ||||||
|         elif old_status == rego.Invoice.STATUS_VOID: |         elif old_status == commerce.Invoice.STATUS_VOID: | ||||||
|             # Should not ever change from here |             # Should not ever change from here | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -218,7 +220,7 @@ class InvoiceController(object): | ||||||
|         if cart: |         if cart: | ||||||
|             cart.active = False |             cart.active = False | ||||||
|             cart.save() |             cart.save() | ||||||
|         self.invoice.status = rego.Invoice.STATUS_PAID |         self.invoice.status = commerce.Invoice.STATUS_PAID | ||||||
|         self.invoice.save() |         self.invoice.save() | ||||||
| 
 | 
 | ||||||
|     def _mark_refunded(self): |     def _mark_refunded(self): | ||||||
|  | @ -229,13 +231,13 @@ class InvoiceController(object): | ||||||
|             cart.active = False |             cart.active = False | ||||||
|             cart.released = True |             cart.released = True | ||||||
|             cart.save() |             cart.save() | ||||||
|         self.invoice.status = rego.Invoice.STATUS_REFUNDED |         self.invoice.status = commerce.Invoice.STATUS_REFUNDED | ||||||
|         self.invoice.save() |         self.invoice.save() | ||||||
| 
 | 
 | ||||||
|     def _mark_void(self): |     def _mark_void(self): | ||||||
|         ''' Marks the invoice as refunded, and updates the attached cart if |         ''' Marks the invoice as refunded, and updates the attached cart if | ||||||
|         necessary. ''' |         necessary. ''' | ||||||
|         self.invoice.status = rego.Invoice.STATUS_VOID |         self.invoice.status = commerce.Invoice.STATUS_VOID | ||||||
|         self.invoice.save() |         self.invoice.save() | ||||||
| 
 | 
 | ||||||
|     def _invoice_matches_cart(self): |     def _invoice_matches_cart(self): | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import itertools | import itertools | ||||||
| 
 | 
 | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| from category import CategoryController | from category import CategoryController | ||||||
| from conditions import ConditionController | from conditions import ConditionController | ||||||
|  | @ -22,7 +23,7 @@ class ProductController(object): | ||||||
|             raise ValueError("You must provide products or a category") |             raise ValueError("You must provide products or a category") | ||||||
| 
 | 
 | ||||||
|         if category is not None: |         if category is not None: | ||||||
|             all_products = rego.Product.objects.filter(category=category) |             all_products = inventory.Product.objects.filter(category=category) | ||||||
|             all_products = all_products.select_related("category") |             all_products = all_products.select_related("category") | ||||||
|         else: |         else: | ||||||
|             all_products = [] |             all_products = [] | ||||||
|  | @ -65,13 +66,13 @@ class ProductController(object): | ||||||
|             # Don't need to run the remaining queries |             # Don't need to run the remaining queries | ||||||
|             return 999999  # We can do better |             return 999999  # We can do better | ||||||
| 
 | 
 | ||||||
|         carts = rego.Cart.objects.filter( |         carts = commerce.Cart.objects.filter( | ||||||
|             user=user, |             user=user, | ||||||
|             active=False, |             active=False, | ||||||
|             released=False, |             released=False, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         items = rego.ProductItem.objects.filter( |         items = commerce.ProductItem.objects.filter( | ||||||
|             cart__in=carts, |             cart__in=carts, | ||||||
|             product=self.product, |             product=self.product, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
| 
 | 
 | ||||||
| from django import forms | from django import forms | ||||||
| 
 | 
 | ||||||
|  | @ -14,8 +15,8 @@ class ApplyCreditNoteForm(forms.Form): | ||||||
|         self.fields["invoice"].choices = self._unpaid_invoices_for_user |         self.fields["invoice"].choices = self._unpaid_invoices_for_user | ||||||
| 
 | 
 | ||||||
|     def _unpaid_invoices_for_user(self): |     def _unpaid_invoices_for_user(self): | ||||||
|         invoices = rego.Invoice.objects.filter( |         invoices = commerce.Invoice.objects.filter( | ||||||
|             status=rego.Invoice.STATUS_UNPAID, |             status=commerce.Invoice.STATUS_UNPAID, | ||||||
|             user=self.user, |             user=self.user, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -25,7 +26,6 @@ class ApplyCreditNoteForm(forms.Form): | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|     invoice = forms.ChoiceField( |     invoice = forms.ChoiceField( | ||||||
|         #choices=_unpaid_invoices_for_user, |  | ||||||
|         required=True, |         required=True, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | @ -33,14 +33,14 @@ class ApplyCreditNoteForm(forms.Form): | ||||||
| class ManualCreditNoteRefundForm(forms.ModelForm): | class ManualCreditNoteRefundForm(forms.ModelForm): | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = rego.ManualCreditNoteRefund |         model = commerce.ManualCreditNoteRefund | ||||||
|         fields = ["reference"] |         fields = ["reference"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ManualPaymentForm(forms.ModelForm): | class ManualPaymentForm(forms.ModelForm): | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = rego.ManualPayment |         model = commerce.ManualPayment | ||||||
|         fields = ["reference", "amount"] |         fields = ["reference", "amount"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -168,8 +168,8 @@ def ProductsForm(category, products): | ||||||
| 
 | 
 | ||||||
|     # Each Category.RENDER_TYPE value has a subclass here. |     # Each Category.RENDER_TYPE value has a subclass here. | ||||||
|     RENDER_TYPES = { |     RENDER_TYPES = { | ||||||
|         rego.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm, |         inventory.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm, | ||||||
|         rego.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm, |         inventory.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     # Produce a subclass of _ProductsForm which we can alter the base_fields on |     # Produce a subclass of _ProductsForm which we can alter the base_fields on | ||||||
|  |  | ||||||
|  | @ -1,818 +0,0 @@ | ||||||
| from __future__ import unicode_literals |  | ||||||
| 
 |  | ||||||
| import util |  | ||||||
| 
 |  | ||||||
| import datetime |  | ||||||
| import itertools |  | ||||||
| 
 |  | ||||||
| from django.core.exceptions import ObjectDoesNotExist |  | ||||||
| from django.core.exceptions import ValidationError |  | ||||||
| from django.contrib.auth.models import User |  | ||||||
| from django.db import models |  | ||||||
| from django.db.models import F, Q |  | ||||||
| from django.utils import timezone |  | ||||||
| from django.utils.encoding import python_2_unicode_compatible |  | ||||||
| from django.utils.translation import ugettext_lazy as _ |  | ||||||
| from model_utils.managers import InheritanceManager |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # User models |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Attendee(models.Model): |  | ||||||
|     ''' Miscellaneous user-related data. ''' |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "%s" % self.user |  | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def get_instance(user): |  | ||||||
|         ''' Returns the instance of attendee for the given user, or creates |  | ||||||
|         a new one. ''' |  | ||||||
|         try: |  | ||||||
|             return Attendee.objects.get(user=user) |  | ||||||
|         except ObjectDoesNotExist: |  | ||||||
|             return Attendee.objects.create(user=user) |  | ||||||
| 
 |  | ||||||
|     def save(self, *a, **k): |  | ||||||
|         while not self.access_code: |  | ||||||
|             access_code = util.generate_access_code() |  | ||||||
|             if Attendee.objects.filter(access_code=access_code).count() == 0: |  | ||||||
|                 self.access_code = access_code |  | ||||||
|         return super(Attendee, self).save(*a, **k) |  | ||||||
| 
 |  | ||||||
|     user = models.OneToOneField(User, on_delete=models.CASCADE) |  | ||||||
|     # Badge/profile is linked |  | ||||||
|     access_code = models.CharField( |  | ||||||
|         max_length=6, |  | ||||||
|         unique=True, |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     completed_registration = models.BooleanField(default=False) |  | ||||||
|     guided_categories_complete = models.ManyToManyField("category") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class AttendeeProfileBase(models.Model): |  | ||||||
|     ''' Information for an attendee's badge and related preferences. |  | ||||||
|     Subclass this in your Django site to ask for attendee information in your |  | ||||||
|     registration progess. |  | ||||||
|      ''' |  | ||||||
| 
 |  | ||||||
|     objects = InheritanceManager() |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def name_field(cls): |  | ||||||
|         ''' This is used to pre-fill the attendee's name from the |  | ||||||
|         speaker profile. If it's None, that functionality is disabled. ''' |  | ||||||
|         return None |  | ||||||
| 
 |  | ||||||
|     def invoice_recipient(self): |  | ||||||
|         ''' Returns a representation of this attendee profile for the purpose |  | ||||||
|         of rendering to an invoice. Override in subclasses. ''' |  | ||||||
| 
 |  | ||||||
|         # Manual dispatch to subclass. Fleh. |  | ||||||
|         slf = AttendeeProfileBase.objects.get_subclass(id=self.id) |  | ||||||
|         # Actually compare the functions. |  | ||||||
|         if type(slf).invoice_recipient != type(self).invoice_recipient: |  | ||||||
|             return type(slf).invoice_recipient(slf) |  | ||||||
| 
 |  | ||||||
|         # Return a default |  | ||||||
|         return slf.attendee.user.username |  | ||||||
| 
 |  | ||||||
|     attendee = models.OneToOneField(Attendee, on_delete=models.CASCADE) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Inventory Models |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Category(models.Model): |  | ||||||
|     ''' Registration product categories ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("inventory - category") |  | ||||||
|         verbose_name_plural = _("inventory - categories") |  | ||||||
|         ordering = ("order", ) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.name |  | ||||||
| 
 |  | ||||||
|     RENDER_TYPE_RADIO = 1 |  | ||||||
|     RENDER_TYPE_QUANTITY = 2 |  | ||||||
| 
 |  | ||||||
|     CATEGORY_RENDER_TYPES = [ |  | ||||||
|         (RENDER_TYPE_RADIO, _("Radio button")), |  | ||||||
|         (RENDER_TYPE_QUANTITY, _("Quantity boxes")), |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
|     name = models.CharField( |  | ||||||
|         max_length=65, |  | ||||||
|         verbose_name=_("Name"), |  | ||||||
|     ) |  | ||||||
|     description = models.CharField( |  | ||||||
|         max_length=255, |  | ||||||
|         verbose_name=_("Description"), |  | ||||||
|     ) |  | ||||||
|     limit_per_user = models.PositiveIntegerField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         verbose_name=_("Limit per user"), |  | ||||||
|         help_text=_("The total number of items from this category one " |  | ||||||
|                     "attendee may purchase."), |  | ||||||
|     ) |  | ||||||
|     required = models.BooleanField( |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("If enabled, a user must select an " |  | ||||||
|                     "item from this category."), |  | ||||||
|     ) |  | ||||||
|     order = models.PositiveIntegerField( |  | ||||||
|         verbose_name=("Display order"), |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     render_type = models.IntegerField( |  | ||||||
|         choices=CATEGORY_RENDER_TYPES, |  | ||||||
|         verbose_name=_("Render type"), |  | ||||||
|         help_text=_("The registration form will render this category in this " |  | ||||||
|                     "style."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Product(models.Model): |  | ||||||
|     ''' Registration products ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("inventory - product") |  | ||||||
|         ordering = ("category__order", "order") |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "%s - %s" % (self.category.name, self.name) |  | ||||||
| 
 |  | ||||||
|     name = models.CharField( |  | ||||||
|         max_length=65, |  | ||||||
|         verbose_name=_("Name"), |  | ||||||
|     ) |  | ||||||
|     description = models.CharField( |  | ||||||
|         max_length=255, |  | ||||||
|         verbose_name=_("Description"), |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|     ) |  | ||||||
|     category = models.ForeignKey( |  | ||||||
|         Category, |  | ||||||
|         verbose_name=_("Product category") |  | ||||||
|     ) |  | ||||||
|     price = models.DecimalField( |  | ||||||
|         max_digits=8, |  | ||||||
|         decimal_places=2, |  | ||||||
|         verbose_name=_("Price"), |  | ||||||
|     ) |  | ||||||
|     limit_per_user = models.PositiveIntegerField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         verbose_name=_("Limit per user"), |  | ||||||
|     ) |  | ||||||
|     reservation_duration = models.DurationField( |  | ||||||
|         default=datetime.timedelta(hours=1), |  | ||||||
|         verbose_name=_("Reservation duration"), |  | ||||||
|         help_text=_("The length of time this product will be reserved before " |  | ||||||
|                     "it is released for someone else to purchase."), |  | ||||||
|     ) |  | ||||||
|     order = models.PositiveIntegerField( |  | ||||||
|         verbose_name=("Display order"), |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Voucher(models.Model): |  | ||||||
|     ''' Registration vouchers ''' |  | ||||||
| 
 |  | ||||||
|     # Vouchers reserve a cart for a fixed amount of time, so that |  | ||||||
|     # items may be added without the voucher being swiped by someone else |  | ||||||
|     RESERVATION_DURATION = datetime.timedelta(hours=1) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Voucher for %s" % self.recipient |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def normalise_code(cls, code): |  | ||||||
|         return code.upper() |  | ||||||
| 
 |  | ||||||
|     def save(self, *a, **k): |  | ||||||
|         ''' Normalise the voucher code to be uppercase ''' |  | ||||||
|         self.code = self.normalise_code(self.code) |  | ||||||
|         super(Voucher, self).save(*a, **k) |  | ||||||
| 
 |  | ||||||
|     recipient = models.CharField(max_length=64, verbose_name=_("Recipient")) |  | ||||||
|     code = models.CharField(max_length=16, |  | ||||||
|                             unique=True, |  | ||||||
|                             verbose_name=_("Voucher code")) |  | ||||||
|     limit = models.PositiveIntegerField(verbose_name=_("Voucher use limit")) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Product Modifiers |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class DiscountBase(models.Model): |  | ||||||
|     ''' Base class for discounts. Each subclass has controller code that |  | ||||||
|     determines whether or not the given discount is available to be added to |  | ||||||
|     the current cart. ''' |  | ||||||
| 
 |  | ||||||
|     objects = InheritanceManager() |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Discount: " + self.description |  | ||||||
| 
 |  | ||||||
|     def effects(self): |  | ||||||
|         ''' Returns all of the effects of this discount. ''' |  | ||||||
|         products = self.discountforproduct_set.all() |  | ||||||
|         categories = self.discountforcategory_set.all() |  | ||||||
|         return itertools.chain(products, categories) |  | ||||||
| 
 |  | ||||||
|     description = models.CharField( |  | ||||||
|         max_length=255, |  | ||||||
|         verbose_name=_("Description"), |  | ||||||
|         help_text=_("A description of this discount. This will be included on " |  | ||||||
|                     "invoices where this discount is applied."), |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class DiscountForProduct(models.Model): |  | ||||||
|     ''' Represents a discount on an individual product. Each Discount can |  | ||||||
|     contain multiple products and categories. Discounts can either be a |  | ||||||
|     percentage or a fixed amount, but not both. ''' |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         if self.percentage: |  | ||||||
|             return "%s%% off %s" % (self.percentage, self.product) |  | ||||||
|         elif self.price: |  | ||||||
|             return "$%s off %s" % (self.price, self.product) |  | ||||||
| 
 |  | ||||||
|     def clean(self): |  | ||||||
|         if self.percentage is None and self.price is None: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("Discount must have a percentage or a price.")) |  | ||||||
|         elif self.percentage is not None and self.price is not None: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("Discount may only have a percentage or only a price.")) |  | ||||||
| 
 |  | ||||||
|         prods = DiscountForProduct.objects.filter( |  | ||||||
|             discount=self.discount, |  | ||||||
|             product=self.product) |  | ||||||
|         cats = DiscountForCategory.objects.filter( |  | ||||||
|             discount=self.discount, |  | ||||||
|             category=self.product.category) |  | ||||||
|         if len(prods) > 1: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("You may only have one discount line per product")) |  | ||||||
|         if len(cats) != 0: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("You may only have one discount for " |  | ||||||
|                     "a product or its category")) |  | ||||||
| 
 |  | ||||||
|     discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE) |  | ||||||
|     product = models.ForeignKey(Product, on_delete=models.CASCADE) |  | ||||||
|     percentage = models.DecimalField( |  | ||||||
|         max_digits=4, decimal_places=1, null=True, blank=True) |  | ||||||
|     price = models.DecimalField( |  | ||||||
|         max_digits=8, decimal_places=2, null=True, blank=True) |  | ||||||
|     quantity = models.PositiveIntegerField() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class DiscountForCategory(models.Model): |  | ||||||
|     ''' Represents a discount for a category of products. Each discount can |  | ||||||
|     contain multiple products. Category discounts can only be a percentage. ''' |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "%s%% off %s" % (self.percentage, self.category) |  | ||||||
| 
 |  | ||||||
|     def clean(self): |  | ||||||
|         prods = DiscountForProduct.objects.filter( |  | ||||||
|             discount=self.discount, |  | ||||||
|             product__category=self.category) |  | ||||||
|         cats = DiscountForCategory.objects.filter( |  | ||||||
|             discount=self.discount, |  | ||||||
|             category=self.category) |  | ||||||
|         if len(prods) != 0: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("You may only have one discount for " |  | ||||||
|                     "a product or its category")) |  | ||||||
|         if len(cats) > 1: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 _("You may only have one discount line per category")) |  | ||||||
| 
 |  | ||||||
|     discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE) |  | ||||||
|     category = models.ForeignKey(Category, on_delete=models.CASCADE) |  | ||||||
|     percentage = models.DecimalField( |  | ||||||
|         max_digits=4, |  | ||||||
|         decimal_places=1) |  | ||||||
|     quantity = models.PositiveIntegerField() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TimeOrStockLimitDiscount(DiscountBase): |  | ||||||
|     ''' Discounts that are generally available, but are limited by timespan or |  | ||||||
|     usage count. This is for e.g. Early Bird discounts. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("discount (time/stock limit)") |  | ||||||
|         verbose_name_plural = _("discounts (time/stock limit)") |  | ||||||
| 
 |  | ||||||
|     start_time = models.DateTimeField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         verbose_name=_("Start time"), |  | ||||||
|         help_text=_("This discount will only be available after this time."), |  | ||||||
|     ) |  | ||||||
|     end_time = models.DateTimeField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         verbose_name=_("End time"), |  | ||||||
|         help_text=_("This discount will only be available before this time."), |  | ||||||
|     ) |  | ||||||
|     limit = models.PositiveIntegerField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         verbose_name=_("Limit"), |  | ||||||
|         help_text=_("This discount may only be applied this many times."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class VoucherDiscount(DiscountBase): |  | ||||||
|     ''' Discounts that are enabled when a voucher code is in the current |  | ||||||
|     cart. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("discount (enabled by voucher)") |  | ||||||
|         verbose_name_plural = _("discounts (enabled by voucher)") |  | ||||||
| 
 |  | ||||||
|     voucher = models.OneToOneField( |  | ||||||
|         Voucher, |  | ||||||
|         on_delete=models.CASCADE, |  | ||||||
|         verbose_name=_("Voucher"), |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class IncludedProductDiscount(DiscountBase): |  | ||||||
|     ''' Discounts that are enabled because another product has been purchased. |  | ||||||
|     e.g. A conference ticket includes a free t-shirt. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("discount (product inclusions)") |  | ||||||
|         verbose_name_plural = _("discounts (product inclusions)") |  | ||||||
| 
 |  | ||||||
|     enabling_products = models.ManyToManyField( |  | ||||||
|         Product, |  | ||||||
|         verbose_name=_("Including product"), |  | ||||||
|         help_text=_("If one of these products are purchased, the discounts " |  | ||||||
|                     "below will be enabled."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class RoleDiscount(object): |  | ||||||
|     ''' Discounts that are enabled because the active user has a specific |  | ||||||
|     role. This is for e.g. volunteers who can get a discount ticket. ''' |  | ||||||
|     # TODO: implement RoleDiscount |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class FlagBase(models.Model): |  | ||||||
|     ''' This defines a condition which allows products or categories to |  | ||||||
|     be made visible, or be prevented from being visible. |  | ||||||
| 
 |  | ||||||
|     The various subclasses of this can define the conditions that enable |  | ||||||
|     or disable products, by the following rules: |  | ||||||
| 
 |  | ||||||
|     If there is at least one 'disable if false' flag defined on a product or |  | ||||||
|     category, all such flag conditions must be met. If there is at least one |  | ||||||
|     'enable if true' flag, at least one such condition must be met. |  | ||||||
| 
 |  | ||||||
|     If both types of conditions exist on a product, both of these rules apply. |  | ||||||
|     ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         # TODO: make concrete once https://code.djangoproject.com/ticket/26488 |  | ||||||
|         # is solved. |  | ||||||
|         abstract = True |  | ||||||
| 
 |  | ||||||
|     DISABLE_IF_FALSE = 1 |  | ||||||
|     ENABLE_IF_TRUE = 2 |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.description |  | ||||||
| 
 |  | ||||||
|     def effects(self): |  | ||||||
|         ''' Returns all of the items affected by this condition. ''' |  | ||||||
|         return itertools.chain(self.products.all(), self.categories.all()) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_disable_if_false(self): |  | ||||||
|         return self.condition == FlagBase.DISABLE_IF_FALSE |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_enable_if_true(self): |  | ||||||
|         return self.condition == FlagBase.ENABLE_IF_TRUE |  | ||||||
| 
 |  | ||||||
|     description = models.CharField(max_length=255) |  | ||||||
|     condition = models.IntegerField( |  | ||||||
|         default=ENABLE_IF_TRUE, |  | ||||||
|         choices=( |  | ||||||
|             (DISABLE_IF_FALSE, _("Disable if false")), |  | ||||||
|             (ENABLE_IF_TRUE, _("Enable if true")), |  | ||||||
|         ), |  | ||||||
|         help_text=_("If there is at least one 'disable if false' flag " |  | ||||||
|                     "defined on a product or category, all such flag " |  | ||||||
|                     " conditions must be met. If there is at least one " |  | ||||||
|                     "'enable if true' flag, at least one such condition must " |  | ||||||
|                     "be met. If both types of conditions exist on a product, " |  | ||||||
|                     "both of these rules apply." |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     products = models.ManyToManyField( |  | ||||||
|         Product, |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("Products affected by this flag's condition."), |  | ||||||
|         related_name="flagbase_set", |  | ||||||
|     ) |  | ||||||
|     categories = models.ManyToManyField( |  | ||||||
|         Category, |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("Categories whose products are affected by this flag's " |  | ||||||
|                     "condition." |  | ||||||
|         ), |  | ||||||
|         related_name="flagbase_set", |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class EnablingConditionBase(FlagBase): |  | ||||||
|     ''' Reifies the abstract FlagBase. This is necessary because django |  | ||||||
|     prevents renaming base classes in migrations. ''' |  | ||||||
|     # TODO: remove this, and make subclasses subclass FlagBase once |  | ||||||
|     # https://code.djangoproject.com/ticket/26488 is solved. |  | ||||||
| 
 |  | ||||||
|     objects = InheritanceManager() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TimeOrStockLimitFlag(EnablingConditionBase): |  | ||||||
|     ''' Registration product ceilings ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("flag (time/stock limit)") |  | ||||||
|         verbose_name_plural = _("flags (time/stock limit)") |  | ||||||
| 
 |  | ||||||
|     start_time = models.DateTimeField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("Products included in this condition will only be " |  | ||||||
|                     "available after this time."), |  | ||||||
|     ) |  | ||||||
|     end_time = models.DateTimeField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("Products included in this condition will only be " |  | ||||||
|                     "available before this time."), |  | ||||||
|     ) |  | ||||||
|     limit = models.PositiveIntegerField( |  | ||||||
|         null=True, |  | ||||||
|         blank=True, |  | ||||||
|         help_text=_("The number of items under this grouping that can be " |  | ||||||
|                     "purchased."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class ProductFlag(EnablingConditionBase): |  | ||||||
|     ''' The condition is met because a specific product is purchased. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("flag (dependency on product)") |  | ||||||
|         verbose_name_plural = _("flags (dependency on product)") |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Enabled by products: " + str(self.enabling_products.all()) |  | ||||||
| 
 |  | ||||||
|     enabling_products = models.ManyToManyField( |  | ||||||
|         Product, |  | ||||||
|         help_text=_("If one of these products are purchased, this condition " |  | ||||||
|                     "is met."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class CategoryFlag(EnablingConditionBase): |  | ||||||
|     ''' The condition is met because a product in a particular product is |  | ||||||
|     purchased. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("flag (dependency on product from category)") |  | ||||||
|         verbose_name_plural = _("flags (dependency on product from category)") |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Enabled by product in category: " + str(self.enabling_category) |  | ||||||
| 
 |  | ||||||
|     enabling_category = models.ForeignKey( |  | ||||||
|         Category, |  | ||||||
|         help_text=_("If a product from this category is purchased, this " |  | ||||||
|                     "condition is met."), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class VoucherFlag(EnablingConditionBase): |  | ||||||
|     ''' The condition is met because a Voucher is present. This is for e.g. |  | ||||||
|     enabling sponsor tickets. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("flag (dependency on voucher)") |  | ||||||
|         verbose_name_plural = _("flags (dependency on voucher)") |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Enabled by voucher: %s" % self.voucher |  | ||||||
| 
 |  | ||||||
|     voucher = models.OneToOneField(Voucher) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # @python_2_unicode_compatible |  | ||||||
| class RoleFlag(object): |  | ||||||
|     ''' The condition is met because the active user has a particular Role. |  | ||||||
|     This is for e.g. enabling Team tickets. ''' |  | ||||||
|     # TODO: implement RoleFlag |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Commerce Models |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Cart(models.Model): |  | ||||||
|     ''' Represents a set of product items that have been purchased, or are |  | ||||||
|     pending purchase. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         index_together = [ |  | ||||||
|             ("active", "time_last_updated"), |  | ||||||
|             ("active", "released"), |  | ||||||
|             ("active", "user"), |  | ||||||
|             ("released", "user"), |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "%d rev #%d" % (self.id, self.revision) |  | ||||||
| 
 |  | ||||||
|     user = models.ForeignKey(User) |  | ||||||
|     # ProductItems (foreign key) |  | ||||||
|     vouchers = models.ManyToManyField(Voucher, blank=True) |  | ||||||
|     time_last_updated = models.DateTimeField( |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     reservation_duration = models.DurationField() |  | ||||||
|     revision = models.PositiveIntegerField(default=1) |  | ||||||
|     active = models.BooleanField( |  | ||||||
|         default=True, |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     released = models.BooleanField( |  | ||||||
|         default=False, |  | ||||||
|         db_index=True |  | ||||||
|     )  # Refunds etc |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def reserved_carts(cls): |  | ||||||
|         ''' Gets all carts that are 'reserved' ''' |  | ||||||
|         return Cart.objects.filter( |  | ||||||
|             (Q(active=True) & |  | ||||||
|                 Q(time_last_updated__gt=( |  | ||||||
|                     timezone.now()-F('reservation_duration') |  | ||||||
|                                         ))) | |  | ||||||
|             (Q(active=False) & Q(released=False)) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class ProductItem(models.Model): |  | ||||||
|     ''' Represents a product-quantity pair in a Cart. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         ordering = ("product", ) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "product: %s * %d in Cart: %s" % ( |  | ||||||
|             self.product, self.quantity, self.cart) |  | ||||||
| 
 |  | ||||||
|     cart = models.ForeignKey(Cart) |  | ||||||
|     product = models.ForeignKey(Product) |  | ||||||
|     quantity = models.PositiveIntegerField(db_index=True) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class DiscountItem(models.Model): |  | ||||||
|     ''' Represents a discount-product-quantity relation in a Cart. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         ordering = ("product", ) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "%s: %s * %d in Cart: %s" % ( |  | ||||||
|             self.discount, self.product, self.quantity, self.cart) |  | ||||||
| 
 |  | ||||||
|     cart = models.ForeignKey(Cart) |  | ||||||
|     product = models.ForeignKey(Product) |  | ||||||
|     discount = models.ForeignKey(DiscountBase) |  | ||||||
|     quantity = models.PositiveIntegerField() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class Invoice(models.Model): |  | ||||||
|     ''' An invoice. Invoices can be automatically generated when checking out |  | ||||||
|     a Cart, in which case, it is attached to a given revision of a Cart. ''' |  | ||||||
| 
 |  | ||||||
|     STATUS_UNPAID = 1 |  | ||||||
|     STATUS_PAID = 2 |  | ||||||
|     STATUS_REFUNDED = 3 |  | ||||||
|     STATUS_VOID = 4 |  | ||||||
| 
 |  | ||||||
|     STATUS_TYPES = [ |  | ||||||
|         (STATUS_UNPAID, _("Unpaid")), |  | ||||||
|         (STATUS_PAID, _("Paid")), |  | ||||||
|         (STATUS_REFUNDED, _("Refunded")), |  | ||||||
|         (STATUS_VOID, _("VOID")), |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Invoice #%d" % self.id |  | ||||||
| 
 |  | ||||||
|     def clean(self): |  | ||||||
|         if self.cart is not None and self.cart_revision is None: |  | ||||||
|             raise ValidationError( |  | ||||||
|                 "If this is a cart invoice, it must have a revision") |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_unpaid(self): |  | ||||||
|         return self.status == self.STATUS_UNPAID |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_void(self): |  | ||||||
|         return self.status == self.STATUS_VOID |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_paid(self): |  | ||||||
|         return self.status == self.STATUS_PAID |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_refunded(self): |  | ||||||
|         return self.status == self.STATUS_REFUNDED |  | ||||||
| 
 |  | ||||||
|     # Invoice Number |  | ||||||
|     user = models.ForeignKey(User) |  | ||||||
|     cart = models.ForeignKey(Cart, null=True) |  | ||||||
|     cart_revision = models.IntegerField( |  | ||||||
|         null=True, |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     # Line Items (foreign key) |  | ||||||
|     status = models.IntegerField( |  | ||||||
|         choices=STATUS_TYPES, |  | ||||||
|         db_index=True, |  | ||||||
|     ) |  | ||||||
|     recipient = models.CharField(max_length=1024) |  | ||||||
|     issue_time = models.DateTimeField() |  | ||||||
|     due_time = models.DateTimeField() |  | ||||||
|     value = models.DecimalField(max_digits=8, decimal_places=2) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class LineItem(models.Model): |  | ||||||
|     ''' Line items for an invoice. These are denormalised from the ProductItems |  | ||||||
|     and DiscountItems that belong to a cart (for consistency), but also allow |  | ||||||
|     for arbitrary line items when required. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         ordering = ("id", ) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Line: %s * %d @ %s" % ( |  | ||||||
|             self.description, self.quantity, self.price) |  | ||||||
| 
 |  | ||||||
|     invoice = models.ForeignKey(Invoice) |  | ||||||
|     description = models.CharField(max_length=255) |  | ||||||
|     quantity = models.PositiveIntegerField() |  | ||||||
|     price = models.DecimalField(max_digits=8, decimal_places=2) |  | ||||||
|     product = models.ForeignKey(Product, null=True, blank=True) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @python_2_unicode_compatible |  | ||||||
| class PaymentBase(models.Model): |  | ||||||
|     ''' The base payment type for invoices. Payment apps should subclass this |  | ||||||
|     class to handle implementation-specific issues. ''' |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         ordering = ("time", ) |  | ||||||
| 
 |  | ||||||
|     objects = InheritanceManager() |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Payment: ref=%s amount=%s" % (self.reference, self.amount) |  | ||||||
| 
 |  | ||||||
|     invoice = models.ForeignKey(Invoice) |  | ||||||
|     time = models.DateTimeField(default=timezone.now) |  | ||||||
|     reference = models.CharField(max_length=255) |  | ||||||
|     amount = models.DecimalField(max_digits=8, decimal_places=2) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ManualPayment(PaymentBase): |  | ||||||
|     ''' Payments that are manually entered by staff. ''' |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CreditNote(PaymentBase): |  | ||||||
|     ''' Credit notes represent money accounted for in the system that do not |  | ||||||
|     belong to specific invoices. They may be paid into other invoices, or |  | ||||||
|     cashed out as refunds. |  | ||||||
| 
 |  | ||||||
|     Each CreditNote may either be used to pay towards another Invoice in the |  | ||||||
|     system (by attaching a CreditNoteApplication), or may be marked as |  | ||||||
|     refunded (by attaching a CreditNoteRefund).''' |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def unclaimed(cls): |  | ||||||
|         return cls.objects.filter( |  | ||||||
|             creditnoteapplication=None, |  | ||||||
|             creditnoterefund=None, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def status(self): |  | ||||||
|         if self.is_unclaimed: |  | ||||||
|             return "Unclaimed" |  | ||||||
| 
 |  | ||||||
|         if hasattr(self, 'creditnoteapplication'): |  | ||||||
|             destination = self.creditnoteapplication.invoice.id |  | ||||||
|             return "Applied to invoice %d" % destination |  | ||||||
| 
 |  | ||||||
|         elif hasattr(self, 'creditnoterefund'): |  | ||||||
|             reference = self.creditnoterefund.reference |  | ||||||
|             print reference |  | ||||||
|             return "Refunded with reference: %s" % reference |  | ||||||
| 
 |  | ||||||
|         raise ValueError("This should never happen.") |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def is_unclaimed(self): |  | ||||||
|         return not ( |  | ||||||
|             hasattr(self, 'creditnoterefund') or |  | ||||||
|             hasattr(self, 'creditnoteapplication') |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def value(self): |  | ||||||
|         ''' Returns the value of the credit note. Because CreditNotes are |  | ||||||
|         implemented as PaymentBase objects internally, the amount is a |  | ||||||
|         negative payment against an invoice. ''' |  | ||||||
|         return -self.amount |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CleanOnSave(object): |  | ||||||
| 
 |  | ||||||
|     def save(self, *a, **k): |  | ||||||
|         self.full_clean() |  | ||||||
|         super(CleanOnSave, self).save(*a, **k) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CreditNoteApplication(CleanOnSave, PaymentBase): |  | ||||||
|     ''' Represents an application of a credit note to an Invoice. ''' |  | ||||||
| 
 |  | ||||||
|     def clean(self): |  | ||||||
|         if not hasattr(self, "parent"): |  | ||||||
|             return |  | ||||||
|         if hasattr(self.parent, 'creditnoterefund'): |  | ||||||
|             raise ValidationError( |  | ||||||
|                 "Cannot apply a refunded credit note to an invoice" |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|     parent = models.OneToOneField(CreditNote) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CreditNoteRefund(CleanOnSave, models.Model): |  | ||||||
|     ''' Represents a refund of a credit note to an external payment. |  | ||||||
|     Credit notes may only be refunded in full. How those refunds are handled |  | ||||||
|     is left as an exercise to the payment app. ''' |  | ||||||
| 
 |  | ||||||
|     def clean(self): |  | ||||||
|         if not hasattr(self, "parent"): |  | ||||||
|             return |  | ||||||
|         if hasattr(self.parent, 'creditnoteapplication'): |  | ||||||
|             raise ValidationError( |  | ||||||
|                 "Cannot refund a credit note that has been paid to an invoice" |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|     parent = models.OneToOneField(CreditNote) |  | ||||||
|     time = models.DateTimeField(default=timezone.now) |  | ||||||
|     reference = models.CharField(max_length=255) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ManualCreditNoteRefund(CreditNoteRefund): |  | ||||||
|     ''' Credit notes that are entered by a staff member. ''' |  | ||||||
| 
 |  | ||||||
|     entered_by = models.ForeignKey(User) |  | ||||||
							
								
								
									
										4
									
								
								registrasion/models/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								registrasion/models/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | from commerce import *  # NOQA | ||||||
|  | from conditions import *  # NOQA | ||||||
|  | from inventory import *  # NOQA | ||||||
|  | from people import *  # NOQA | ||||||
							
								
								
									
										304
									
								
								registrasion/models/commerce.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								registrasion/models/commerce.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,304 @@ | ||||||
|  | from . import conditions | ||||||
|  | from . import inventory | ||||||
|  | 
 | ||||||
|  | from django.contrib.auth.models import User | ||||||
|  | from django.core.exceptions import ValidationError | ||||||
|  | from django.db import models | ||||||
|  | from django.db.models import F, Q | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.utils.encoding import python_2_unicode_compatible | ||||||
|  | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | from model_utils.managers import InheritanceManager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Commerce Models | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Cart(models.Model): | ||||||
|  |     ''' Represents a set of product items that have been purchased, or are | ||||||
|  |     pending purchase. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         index_together = [ | ||||||
|  |             ("active", "time_last_updated"), | ||||||
|  |             ("active", "released"), | ||||||
|  |             ("active", "user"), | ||||||
|  |             ("released", "user"), | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "%d rev #%d" % (self.id, self.revision) | ||||||
|  | 
 | ||||||
|  |     user = models.ForeignKey(User) | ||||||
|  |     # ProductItems (foreign key) | ||||||
|  |     vouchers = models.ManyToManyField(inventory.Voucher, blank=True) | ||||||
|  |     time_last_updated = models.DateTimeField( | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     reservation_duration = models.DurationField() | ||||||
|  |     revision = models.PositiveIntegerField(default=1) | ||||||
|  |     active = models.BooleanField( | ||||||
|  |         default=True, | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     released = models.BooleanField( | ||||||
|  |         default=False, | ||||||
|  |         db_index=True | ||||||
|  |     )  # Refunds etc | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def reserved_carts(cls): | ||||||
|  |         ''' Gets all carts that are 'reserved' ''' | ||||||
|  |         return Cart.objects.filter( | ||||||
|  |             (Q(active=True) & | ||||||
|  |                 Q(time_last_updated__gt=( | ||||||
|  |                     timezone.now()-F('reservation_duration') | ||||||
|  |                                         ))) | | ||||||
|  |             (Q(active=False) & Q(released=False)) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class ProductItem(models.Model): | ||||||
|  |     ''' Represents a product-quantity pair in a Cart. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         ordering = ("product", ) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "product: %s * %d in Cart: %s" % ( | ||||||
|  |             self.product, self.quantity, self.cart) | ||||||
|  | 
 | ||||||
|  |     cart = models.ForeignKey(Cart) | ||||||
|  |     product = models.ForeignKey(inventory.Product) | ||||||
|  |     quantity = models.PositiveIntegerField(db_index=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class DiscountItem(models.Model): | ||||||
|  |     ''' Represents a discount-product-quantity relation in a Cart. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         ordering = ("product", ) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "%s: %s * %d in Cart: %s" % ( | ||||||
|  |             self.discount, self.product, self.quantity, self.cart) | ||||||
|  | 
 | ||||||
|  |     cart = models.ForeignKey(Cart) | ||||||
|  |     product = models.ForeignKey(inventory.Product) | ||||||
|  |     discount = models.ForeignKey(conditions.DiscountBase) | ||||||
|  |     quantity = models.PositiveIntegerField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Invoice(models.Model): | ||||||
|  |     ''' An invoice. Invoices can be automatically generated when checking out | ||||||
|  |     a Cart, in which case, it is attached to a given revision of a Cart. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     STATUS_UNPAID = 1 | ||||||
|  |     STATUS_PAID = 2 | ||||||
|  |     STATUS_REFUNDED = 3 | ||||||
|  |     STATUS_VOID = 4 | ||||||
|  | 
 | ||||||
|  |     STATUS_TYPES = [ | ||||||
|  |         (STATUS_UNPAID, _("Unpaid")), | ||||||
|  |         (STATUS_PAID, _("Paid")), | ||||||
|  |         (STATUS_REFUNDED, _("Refunded")), | ||||||
|  |         (STATUS_VOID, _("VOID")), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Invoice #%d" % self.id | ||||||
|  | 
 | ||||||
|  |     def clean(self): | ||||||
|  |         if self.cart is not None and self.cart_revision is None: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 "If this is a cart invoice, it must have a revision") | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_unpaid(self): | ||||||
|  |         return self.status == self.STATUS_UNPAID | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_void(self): | ||||||
|  |         return self.status == self.STATUS_VOID | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_paid(self): | ||||||
|  |         return self.status == self.STATUS_PAID | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_refunded(self): | ||||||
|  |         return self.status == self.STATUS_REFUNDED | ||||||
|  | 
 | ||||||
|  |     # Invoice Number | ||||||
|  |     user = models.ForeignKey(User) | ||||||
|  |     cart = models.ForeignKey(Cart, null=True) | ||||||
|  |     cart_revision = models.IntegerField( | ||||||
|  |         null=True, | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     # Line Items (foreign key) | ||||||
|  |     status = models.IntegerField( | ||||||
|  |         choices=STATUS_TYPES, | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     recipient = models.CharField(max_length=1024) | ||||||
|  |     issue_time = models.DateTimeField() | ||||||
|  |     due_time = models.DateTimeField() | ||||||
|  |     value = models.DecimalField(max_digits=8, decimal_places=2) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class LineItem(models.Model): | ||||||
|  |     ''' Line items for an invoice. These are denormalised from the ProductItems | ||||||
|  |     and DiscountItems that belong to a cart (for consistency), but also allow | ||||||
|  |     for arbitrary line items when required. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         ordering = ("id", ) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Line: %s * %d @ %s" % ( | ||||||
|  |             self.description, self.quantity, self.price) | ||||||
|  | 
 | ||||||
|  |     invoice = models.ForeignKey(Invoice) | ||||||
|  |     description = models.CharField(max_length=255) | ||||||
|  |     quantity = models.PositiveIntegerField() | ||||||
|  |     price = models.DecimalField(max_digits=8, decimal_places=2) | ||||||
|  |     product = models.ForeignKey(inventory.Product, null=True, blank=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class PaymentBase(models.Model): | ||||||
|  |     ''' The base payment type for invoices. Payment apps should subclass this | ||||||
|  |     class to handle implementation-specific issues. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         ordering = ("time", ) | ||||||
|  | 
 | ||||||
|  |     objects = InheritanceManager() | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Payment: ref=%s amount=%s" % (self.reference, self.amount) | ||||||
|  | 
 | ||||||
|  |     invoice = models.ForeignKey(Invoice) | ||||||
|  |     time = models.DateTimeField(default=timezone.now) | ||||||
|  |     reference = models.CharField(max_length=255) | ||||||
|  |     amount = models.DecimalField(max_digits=8, decimal_places=2) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ManualPayment(PaymentBase): | ||||||
|  |     ''' Payments that are manually entered by staff. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CreditNote(PaymentBase): | ||||||
|  |     ''' Credit notes represent money accounted for in the system that do not | ||||||
|  |     belong to specific invoices. They may be paid into other invoices, or | ||||||
|  |     cashed out as refunds. | ||||||
|  | 
 | ||||||
|  |     Each CreditNote may either be used to pay towards another Invoice in the | ||||||
|  |     system (by attaching a CreditNoteApplication), or may be marked as | ||||||
|  |     refunded (by attaching a CreditNoteRefund).''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def unclaimed(cls): | ||||||
|  |         return cls.objects.filter( | ||||||
|  |             creditnoteapplication=None, | ||||||
|  |             creditnoterefund=None, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def status(self): | ||||||
|  |         if self.is_unclaimed: | ||||||
|  |             return "Unclaimed" | ||||||
|  | 
 | ||||||
|  |         if hasattr(self, 'creditnoteapplication'): | ||||||
|  |             destination = self.creditnoteapplication.invoice.id | ||||||
|  |             return "Applied to invoice %d" % destination | ||||||
|  | 
 | ||||||
|  |         elif hasattr(self, 'creditnoterefund'): | ||||||
|  |             reference = self.creditnoterefund.reference | ||||||
|  |             print reference | ||||||
|  |             return "Refunded with reference: %s" % reference | ||||||
|  | 
 | ||||||
|  |         raise ValueError("This should never happen.") | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_unclaimed(self): | ||||||
|  |         return not ( | ||||||
|  |             hasattr(self, 'creditnoterefund') or | ||||||
|  |             hasattr(self, 'creditnoteapplication') | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         ''' Returns the value of the credit note. Because CreditNotes are | ||||||
|  |         implemented as PaymentBase objects internally, the amount is a | ||||||
|  |         negative payment against an invoice. ''' | ||||||
|  |         return -self.amount | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CleanOnSave(object): | ||||||
|  | 
 | ||||||
|  |     def save(self, *a, **k): | ||||||
|  |         self.full_clean() | ||||||
|  |         super(CleanOnSave, self).save(*a, **k) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CreditNoteApplication(CleanOnSave, PaymentBase): | ||||||
|  |     ''' Represents an application of a credit note to an Invoice. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     def clean(self): | ||||||
|  |         if not hasattr(self, "parent"): | ||||||
|  |             return | ||||||
|  |         if hasattr(self.parent, 'creditnoterefund'): | ||||||
|  |             raise ValidationError( | ||||||
|  |                 "Cannot apply a refunded credit note to an invoice" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     parent = models.OneToOneField(CreditNote) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CreditNoteRefund(CleanOnSave, models.Model): | ||||||
|  |     ''' Represents a refund of a credit note to an external payment. | ||||||
|  |     Credit notes may only be refunded in full. How those refunds are handled | ||||||
|  |     is left as an exercise to the payment app. ''' | ||||||
|  | 
 | ||||||
|  |     def clean(self): | ||||||
|  |         if not hasattr(self, "parent"): | ||||||
|  |             return | ||||||
|  |         if hasattr(self.parent, 'creditnoteapplication'): | ||||||
|  |             raise ValidationError( | ||||||
|  |                 "Cannot refund a credit note that has been paid to an invoice" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     parent = models.OneToOneField(CreditNote) | ||||||
|  |     time = models.DateTimeField(default=timezone.now) | ||||||
|  |     reference = models.CharField(max_length=255) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ManualCreditNoteRefund(CreditNoteRefund): | ||||||
|  |     ''' Credit notes that are entered by a staff member. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     entered_by = models.ForeignKey(User) | ||||||
							
								
								
									
										361
									
								
								registrasion/models/conditions.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								registrasion/models/conditions.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,361 @@ | ||||||
|  | import itertools | ||||||
|  | 
 | ||||||
|  | from . import inventory | ||||||
|  | 
 | ||||||
|  | from django.core.exceptions import ValidationError | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.encoding import python_2_unicode_compatible | ||||||
|  | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | from model_utils.managers import InheritanceManager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Product Modifiers | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class DiscountBase(models.Model): | ||||||
|  |     ''' Base class for discounts. Each subclass has controller code that | ||||||
|  |     determines whether or not the given discount is available to be added to | ||||||
|  |     the current cart. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     objects = InheritanceManager() | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Discount: " + self.description | ||||||
|  | 
 | ||||||
|  |     def effects(self): | ||||||
|  |         ''' Returns all of the effects of this discount. ''' | ||||||
|  |         products = self.discountforproduct_set.all() | ||||||
|  |         categories = self.discountforcategory_set.all() | ||||||
|  |         return itertools.chain(products, categories) | ||||||
|  | 
 | ||||||
|  |     description = models.CharField( | ||||||
|  |         max_length=255, | ||||||
|  |         verbose_name=_("Description"), | ||||||
|  |         help_text=_("A description of this discount. This will be included on " | ||||||
|  |                     "invoices where this discount is applied."), | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class DiscountForProduct(models.Model): | ||||||
|  |     ''' Represents a discount on an individual product. Each Discount can | ||||||
|  |     contain multiple products and categories. Discounts can either be a | ||||||
|  |     percentage or a fixed amount, but not both. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         if self.percentage: | ||||||
|  |             return "%s%% off %s" % (self.percentage, self.product) | ||||||
|  |         elif self.price: | ||||||
|  |             return "$%s off %s" % (self.price, self.product) | ||||||
|  | 
 | ||||||
|  |     def clean(self): | ||||||
|  |         if self.percentage is None and self.price is None: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("Discount must have a percentage or a price.")) | ||||||
|  |         elif self.percentage is not None and self.price is not None: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("Discount may only have a percentage or only a price.")) | ||||||
|  | 
 | ||||||
|  |         prods = DiscountForProduct.objects.filter( | ||||||
|  |             discount=self.discount, | ||||||
|  |             product=self.product) | ||||||
|  |         cats = DiscountForCategory.objects.filter( | ||||||
|  |             discount=self.discount, | ||||||
|  |             category=self.product.category) | ||||||
|  |         if len(prods) > 1: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("You may only have one discount line per product")) | ||||||
|  |         if len(cats) != 0: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("You may only have one discount for " | ||||||
|  |                     "a product or its category")) | ||||||
|  | 
 | ||||||
|  |     discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE) | ||||||
|  |     product = models.ForeignKey(inventory.Product, on_delete=models.CASCADE) | ||||||
|  |     percentage = models.DecimalField( | ||||||
|  |         max_digits=4, decimal_places=1, null=True, blank=True) | ||||||
|  |     price = models.DecimalField( | ||||||
|  |         max_digits=8, decimal_places=2, null=True, blank=True) | ||||||
|  |     quantity = models.PositiveIntegerField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class DiscountForCategory(models.Model): | ||||||
|  |     ''' Represents a discount for a category of products. Each discount can | ||||||
|  |     contain multiple products. Category discounts can only be a percentage. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "%s%% off %s" % (self.percentage, self.category) | ||||||
|  | 
 | ||||||
|  |     def clean(self): | ||||||
|  |         prods = DiscountForProduct.objects.filter( | ||||||
|  |             discount=self.discount, | ||||||
|  |             product__category=self.category) | ||||||
|  |         cats = DiscountForCategory.objects.filter( | ||||||
|  |             discount=self.discount, | ||||||
|  |             category=self.category) | ||||||
|  |         if len(prods) != 0: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("You may only have one discount for " | ||||||
|  |                     "a product or its category")) | ||||||
|  |         if len(cats) > 1: | ||||||
|  |             raise ValidationError( | ||||||
|  |                 _("You may only have one discount line per category")) | ||||||
|  | 
 | ||||||
|  |     discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE) | ||||||
|  |     category = models.ForeignKey(inventory.Category, on_delete=models.CASCADE) | ||||||
|  |     percentage = models.DecimalField( | ||||||
|  |         max_digits=4, | ||||||
|  |         decimal_places=1) | ||||||
|  |     quantity = models.PositiveIntegerField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TimeOrStockLimitDiscount(DiscountBase): | ||||||
|  |     ''' Discounts that are generally available, but are limited by timespan or | ||||||
|  |     usage count. This is for e.g. Early Bird discounts. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("discount (time/stock limit)") | ||||||
|  |         verbose_name_plural = _("discounts (time/stock limit)") | ||||||
|  | 
 | ||||||
|  |     start_time = models.DateTimeField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         verbose_name=_("Start time"), | ||||||
|  |         help_text=_("This discount will only be available after this time."), | ||||||
|  |     ) | ||||||
|  |     end_time = models.DateTimeField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         verbose_name=_("End time"), | ||||||
|  |         help_text=_("This discount will only be available before this time."), | ||||||
|  |     ) | ||||||
|  |     limit = models.PositiveIntegerField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         verbose_name=_("Limit"), | ||||||
|  |         help_text=_("This discount may only be applied this many times."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VoucherDiscount(DiscountBase): | ||||||
|  |     ''' Discounts that are enabled when a voucher code is in the current | ||||||
|  |     cart. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("discount (enabled by voucher)") | ||||||
|  |         verbose_name_plural = _("discounts (enabled by voucher)") | ||||||
|  | 
 | ||||||
|  |     voucher = models.OneToOneField( | ||||||
|  |         inventory.Voucher, | ||||||
|  |         on_delete=models.CASCADE, | ||||||
|  |         verbose_name=_("Voucher"), | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IncludedProductDiscount(DiscountBase): | ||||||
|  |     ''' Discounts that are enabled because another product has been purchased. | ||||||
|  |     e.g. A conference ticket includes a free t-shirt. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("discount (product inclusions)") | ||||||
|  |         verbose_name_plural = _("discounts (product inclusions)") | ||||||
|  | 
 | ||||||
|  |     enabling_products = models.ManyToManyField( | ||||||
|  |         inventory.Product, | ||||||
|  |         verbose_name=_("Including product"), | ||||||
|  |         help_text=_("If one of these products are purchased, the discounts " | ||||||
|  |                     "below will be enabled."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class RoleDiscount(object): | ||||||
|  |     ''' Discounts that are enabled because the active user has a specific | ||||||
|  |     role. This is for e.g. volunteers who can get a discount ticket. ''' | ||||||
|  |     # TODO: implement RoleDiscount | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class FlagBase(models.Model): | ||||||
|  |     ''' This defines a condition which allows products or categories to | ||||||
|  |     be made visible, or be prevented from being visible. | ||||||
|  | 
 | ||||||
|  |     The various subclasses of this can define the conditions that enable | ||||||
|  |     or disable products, by the following rules: | ||||||
|  | 
 | ||||||
|  |     If there is at least one 'disable if false' flag defined on a product or | ||||||
|  |     category, all such flag conditions must be met. If there is at least one | ||||||
|  |     'enable if true' flag, at least one such condition must be met. | ||||||
|  | 
 | ||||||
|  |     If both types of conditions exist on a product, both of these rules apply. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         # TODO: make concrete once https://code.djangoproject.com/ticket/26488 | ||||||
|  |         # is solved. | ||||||
|  |         abstract = True | ||||||
|  | 
 | ||||||
|  |     DISABLE_IF_FALSE = 1 | ||||||
|  |     ENABLE_IF_TRUE = 2 | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.description | ||||||
|  | 
 | ||||||
|  |     def effects(self): | ||||||
|  |         ''' Returns all of the items affected by this condition. ''' | ||||||
|  |         return itertools.chain(self.products.all(), self.categories.all()) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_disable_if_false(self): | ||||||
|  |         return self.condition == FlagBase.DISABLE_IF_FALSE | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_enable_if_true(self): | ||||||
|  |         return self.condition == FlagBase.ENABLE_IF_TRUE | ||||||
|  | 
 | ||||||
|  |     description = models.CharField(max_length=255) | ||||||
|  |     condition = models.IntegerField( | ||||||
|  |         default=ENABLE_IF_TRUE, | ||||||
|  |         choices=( | ||||||
|  |             (DISABLE_IF_FALSE, _("Disable if false")), | ||||||
|  |             (ENABLE_IF_TRUE, _("Enable if true")), | ||||||
|  |         ), | ||||||
|  |         help_text=_("If there is at least one 'disable if false' flag " | ||||||
|  |                     "defined on a product or category, all such flag " | ||||||
|  |                     " conditions must be met. If there is at least one " | ||||||
|  |                     "'enable if true' flag, at least one such condition must " | ||||||
|  |                     "be met. If both types of conditions exist on a product, " | ||||||
|  |                     "both of these rules apply." | ||||||
|  |                     ), | ||||||
|  |     ) | ||||||
|  |     products = models.ManyToManyField( | ||||||
|  |         inventory.Product, | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("Products affected by this flag's condition."), | ||||||
|  |         related_name="flagbase_set", | ||||||
|  |     ) | ||||||
|  |     categories = models.ManyToManyField( | ||||||
|  |         inventory.Category, | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("Categories whose products are affected by this flag's " | ||||||
|  |                     "condition." | ||||||
|  |                     ), | ||||||
|  |         related_name="flagbase_set", | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class EnablingConditionBase(FlagBase): | ||||||
|  |     ''' Reifies the abstract FlagBase. This is necessary because django | ||||||
|  |     prevents renaming base classes in migrations. ''' | ||||||
|  |     # TODO: remove this, and make subclasses subclass FlagBase once | ||||||
|  |     # https://code.djangoproject.com/ticket/26488 is solved. | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     objects = InheritanceManager() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TimeOrStockLimitFlag(EnablingConditionBase): | ||||||
|  |     ''' Registration product ceilings ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("flag (time/stock limit)") | ||||||
|  |         verbose_name_plural = _("flags (time/stock limit)") | ||||||
|  | 
 | ||||||
|  |     start_time = models.DateTimeField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("Products included in this condition will only be " | ||||||
|  |                     "available after this time."), | ||||||
|  |     ) | ||||||
|  |     end_time = models.DateTimeField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("Products included in this condition will only be " | ||||||
|  |                     "available before this time."), | ||||||
|  |     ) | ||||||
|  |     limit = models.PositiveIntegerField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("The number of items under this grouping that can be " | ||||||
|  |                     "purchased."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class ProductFlag(EnablingConditionBase): | ||||||
|  |     ''' The condition is met because a specific product is purchased. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("flag (dependency on product)") | ||||||
|  |         verbose_name_plural = _("flags (dependency on product)") | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Enabled by products: " + str(self.enabling_products.all()) | ||||||
|  | 
 | ||||||
|  |     enabling_products = models.ManyToManyField( | ||||||
|  |         inventory.Product, | ||||||
|  |         help_text=_("If one of these products are purchased, this condition " | ||||||
|  |                     "is met."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class CategoryFlag(EnablingConditionBase): | ||||||
|  |     ''' The condition is met because a product in a particular product is | ||||||
|  |     purchased. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("flag (dependency on product from category)") | ||||||
|  |         verbose_name_plural = _("flags (dependency on product from category)") | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Enabled by product in category: " + str(self.enabling_category) | ||||||
|  | 
 | ||||||
|  |     enabling_category = models.ForeignKey( | ||||||
|  |         inventory.Category, | ||||||
|  |         help_text=_("If a product from this category is purchased, this " | ||||||
|  |                     "condition is met."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class VoucherFlag(EnablingConditionBase): | ||||||
|  |     ''' The condition is met because a Voucher is present. This is for e.g. | ||||||
|  |     enabling sponsor tickets. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("flag (dependency on voucher)") | ||||||
|  |         verbose_name_plural = _("flags (dependency on voucher)") | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Enabled by voucher: %s" % self.voucher | ||||||
|  | 
 | ||||||
|  |     voucher = models.OneToOneField(inventory.Voucher) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # @python_2_unicode_compatible | ||||||
|  | class RoleFlag(object): | ||||||
|  |     ''' The condition is met because the active user has a particular Role. | ||||||
|  |     This is for e.g. enabling Team tickets. ''' | ||||||
|  |     # TODO: implement RoleFlag | ||||||
|  |     pass | ||||||
							
								
								
									
										172
									
								
								registrasion/models/inventory.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								registrasion/models/inventory.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,172 @@ | ||||||
|  | import datetime | ||||||
|  | 
 | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.encoding import python_2_unicode_compatible | ||||||
|  | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Inventory Models | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Category(models.Model): | ||||||
|  |     ''' Registration product categories, used as logical groupings for Products | ||||||
|  |     in registration forms. | ||||||
|  | 
 | ||||||
|  |     Attributes: | ||||||
|  |         name (str): The display name for the category. | ||||||
|  | 
 | ||||||
|  |         description (str): Some explanatory text for the category. This is | ||||||
|  |             displayed alongside the forms where your attendees choose their | ||||||
|  |             items. | ||||||
|  | 
 | ||||||
|  |         required (bool): Requires a user to select an item from this category | ||||||
|  |             during initial registration. You can use this, e.g., for making | ||||||
|  |             sure that the user has a ticket before they select whether they | ||||||
|  |             want a t-shirt. | ||||||
|  | 
 | ||||||
|  |         render_type (int): This is used to determine what sort of form the | ||||||
|  |             attendee will be presented with when choosing Products from this | ||||||
|  |             category. These may be either of the following: | ||||||
|  | 
 | ||||||
|  |             ``RENDER_TYPE_RADIO`` presents the Products in the Category as a | ||||||
|  |             list of radio buttons. At most one item can be chosen at a time. | ||||||
|  |             This works well when setting limit_per_user to 1. | ||||||
|  | 
 | ||||||
|  |             ``RENDER_TYPE_QUANTITY`` shows each Product next to an input field, | ||||||
|  |             where the user can specify a quantity of each Product type. This is | ||||||
|  |             useful for additional extras, like Dinner Tickets. | ||||||
|  | 
 | ||||||
|  |         limit_per_user (Optional[int]): This restricts the number of items | ||||||
|  |             from this Category that each attendee may claim. This extends | ||||||
|  |             across multiple Invoices. | ||||||
|  | 
 | ||||||
|  |         display_order (int): An ascending order for displaying the Categories | ||||||
|  |             available. By convention, your Category for ticket types should | ||||||
|  |             have the lowest display order. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("inventory - category") | ||||||
|  |         verbose_name_plural = _("inventory - categories") | ||||||
|  |         ordering = ("order", ) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.name | ||||||
|  | 
 | ||||||
|  |     RENDER_TYPE_RADIO = 1 | ||||||
|  |     RENDER_TYPE_QUANTITY = 2 | ||||||
|  | 
 | ||||||
|  |     CATEGORY_RENDER_TYPES = [ | ||||||
|  |         (RENDER_TYPE_RADIO, _("Radio button")), | ||||||
|  |         (RENDER_TYPE_QUANTITY, _("Quantity boxes")), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     name = models.CharField( | ||||||
|  |         max_length=65, | ||||||
|  |         verbose_name=_("Name"), | ||||||
|  |     ) | ||||||
|  |     description = models.CharField( | ||||||
|  |         max_length=255, | ||||||
|  |         verbose_name=_("Description"), | ||||||
|  |     ) | ||||||
|  |     limit_per_user = models.PositiveIntegerField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         verbose_name=_("Limit per user"), | ||||||
|  |         help_text=_("The total number of items from this category one " | ||||||
|  |                     "attendee may purchase."), | ||||||
|  |     ) | ||||||
|  |     required = models.BooleanField( | ||||||
|  |         blank=True, | ||||||
|  |         help_text=_("If enabled, a user must select an " | ||||||
|  |                     "item from this category."), | ||||||
|  |     ) | ||||||
|  |     order = models.PositiveIntegerField( | ||||||
|  |         verbose_name=("Display order"), | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     render_type = models.IntegerField( | ||||||
|  |         choices=CATEGORY_RENDER_TYPES, | ||||||
|  |         verbose_name=_("Render type"), | ||||||
|  |         help_text=_("The registration form will render this category in this " | ||||||
|  |                     "style."), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Product(models.Model): | ||||||
|  |     ''' Registration products ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  |         verbose_name = _("inventory - product") | ||||||
|  |         ordering = ("category__order", "order") | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "%s - %s" % (self.category.name, self.name) | ||||||
|  | 
 | ||||||
|  |     name = models.CharField( | ||||||
|  |         max_length=65, | ||||||
|  |         verbose_name=_("Name"), | ||||||
|  |     ) | ||||||
|  |     description = models.CharField( | ||||||
|  |         max_length=255, | ||||||
|  |         verbose_name=_("Description"), | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |     ) | ||||||
|  |     category = models.ForeignKey( | ||||||
|  |         Category, | ||||||
|  |         verbose_name=_("Product category") | ||||||
|  |     ) | ||||||
|  |     price = models.DecimalField( | ||||||
|  |         max_digits=8, | ||||||
|  |         decimal_places=2, | ||||||
|  |         verbose_name=_("Price"), | ||||||
|  |     ) | ||||||
|  |     limit_per_user = models.PositiveIntegerField( | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         verbose_name=_("Limit per user"), | ||||||
|  |     ) | ||||||
|  |     reservation_duration = models.DurationField( | ||||||
|  |         default=datetime.timedelta(hours=1), | ||||||
|  |         verbose_name=_("Reservation duration"), | ||||||
|  |         help_text=_("The length of time this product will be reserved before " | ||||||
|  |                     "it is released for someone else to purchase."), | ||||||
|  |     ) | ||||||
|  |     order = models.PositiveIntegerField( | ||||||
|  |         verbose_name=("Display order"), | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Voucher(models.Model): | ||||||
|  |     ''' Registration vouchers ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     # Vouchers reserve a cart for a fixed amount of time, so that | ||||||
|  |     # items may be added without the voucher being swiped by someone else | ||||||
|  |     RESERVATION_DURATION = datetime.timedelta(hours=1) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Voucher for %s" % self.recipient | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def normalise_code(cls, code): | ||||||
|  |         return code.upper() | ||||||
|  | 
 | ||||||
|  |     def save(self, *a, **k): | ||||||
|  |         ''' Normalise the voucher code to be uppercase ''' | ||||||
|  |         self.code = self.normalise_code(self.code) | ||||||
|  |         super(Voucher, self).save(*a, **k) | ||||||
|  | 
 | ||||||
|  |     recipient = models.CharField(max_length=64, verbose_name=_("Recipient")) | ||||||
|  |     code = models.CharField(max_length=16, | ||||||
|  |                             unique=True, | ||||||
|  |                             verbose_name=_("Voucher code")) | ||||||
|  |     limit = models.PositiveIntegerField(verbose_name=_("Voucher use limit")) | ||||||
							
								
								
									
										79
									
								
								registrasion/models/people.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								registrasion/models/people.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | ||||||
|  | from registrasion import util | ||||||
|  | 
 | ||||||
|  | from django.contrib.auth.models import User | ||||||
|  | from django.core.exceptions import ObjectDoesNotExist | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.encoding import python_2_unicode_compatible | ||||||
|  | from model_utils.managers import InheritanceManager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # User models | ||||||
|  | 
 | ||||||
|  | @python_2_unicode_compatible | ||||||
|  | class Attendee(models.Model): | ||||||
|  |     ''' Miscellaneous user-related data. ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "%s" % self.user | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def get_instance(user): | ||||||
|  |         ''' Returns the instance of attendee for the given user, or creates | ||||||
|  |         a new one. ''' | ||||||
|  |         try: | ||||||
|  |             return Attendee.objects.get(user=user) | ||||||
|  |         except ObjectDoesNotExist: | ||||||
|  |             return Attendee.objects.create(user=user) | ||||||
|  | 
 | ||||||
|  |     def save(self, *a, **k): | ||||||
|  |         while not self.access_code: | ||||||
|  |             access_code = util.generate_access_code() | ||||||
|  |             if Attendee.objects.filter(access_code=access_code).count() == 0: | ||||||
|  |                 self.access_code = access_code | ||||||
|  |         return super(Attendee, self).save(*a, **k) | ||||||
|  | 
 | ||||||
|  |     user = models.OneToOneField(User, on_delete=models.CASCADE) | ||||||
|  |     # Badge/profile is linked | ||||||
|  |     access_code = models.CharField( | ||||||
|  |         max_length=6, | ||||||
|  |         unique=True, | ||||||
|  |         db_index=True, | ||||||
|  |     ) | ||||||
|  |     completed_registration = models.BooleanField(default=False) | ||||||
|  |     guided_categories_complete = models.ManyToManyField("category") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AttendeeProfileBase(models.Model): | ||||||
|  |     ''' Information for an attendee's badge and related preferences. | ||||||
|  |     Subclass this in your Django site to ask for attendee information in your | ||||||
|  |     registration progess. | ||||||
|  |      ''' | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         app_label = "registrasion" | ||||||
|  | 
 | ||||||
|  |     objects = InheritanceManager() | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def name_field(cls): | ||||||
|  |         ''' This is used to pre-fill the attendee's name from the | ||||||
|  |         speaker profile. If it's None, that functionality is disabled. ''' | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     def invoice_recipient(self): | ||||||
|  |         ''' Returns a representation of this attendee profile for the purpose | ||||||
|  |         of rendering to an invoice. Override in subclasses. ''' | ||||||
|  | 
 | ||||||
|  |         # Manual dispatch to subclass. Fleh. | ||||||
|  |         slf = AttendeeProfileBase.objects.get_subclass(id=self.id) | ||||||
|  |         # Actually compare the functions. | ||||||
|  |         if type(slf).invoice_recipient != type(self).invoice_recipient: | ||||||
|  |             return type(slf).invoice_recipient(slf) | ||||||
|  | 
 | ||||||
|  |         # Return a default | ||||||
|  |         return slf.attendee.user.username | ||||||
|  | 
 | ||||||
|  |     attendee = models.OneToOneField(Attendee, on_delete=models.CASCADE) | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
| from registrasion.controllers.category import CategoryController | from registrasion.controllers.category import CategoryController | ||||||
| 
 | 
 | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
|  | @ -19,7 +20,7 @@ def available_categories(context): | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def available_credit(context): | def available_credit(context): | ||||||
|     ''' Returns the amount of unclaimed credit available for this user. ''' |     ''' Returns the amount of unclaimed credit available for this user. ''' | ||||||
|     notes = rego.CreditNote.unclaimed().filter( |     notes = commerce.CreditNote.unclaimed().filter( | ||||||
|         invoice__user=context.request.user, |         invoice__user=context.request.user, | ||||||
|     ) |     ) | ||||||
|     ret = notes.values("amount").aggregate(Sum("amount"))["amount__sum"] or 0 |     ret = notes.values("amount").aggregate(Sum("amount"))["amount__sum"] or 0 | ||||||
|  | @ -29,7 +30,7 @@ def available_credit(context): | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def invoices(context): | def invoices(context): | ||||||
|     ''' Returns all of the invoices that this user has. ''' |     ''' Returns all of the invoices that this user has. ''' | ||||||
|     return rego.Invoice.objects.filter(cart__user=context.request.user) |     return commerce.Invoice.objects.filter(cart__user=context.request.user) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
|  | @ -37,7 +38,7 @@ def items_pending(context): | ||||||
|     ''' Returns all of the items that this user has in their current cart, |     ''' Returns all of the items that this user has in their current cart, | ||||||
|     and is awaiting payment. ''' |     and is awaiting payment. ''' | ||||||
| 
 | 
 | ||||||
|     all_items = rego.ProductItem.objects.filter( |     all_items = commerce.ProductItem.objects.filter( | ||||||
|         cart__user=context.request.user, |         cart__user=context.request.user, | ||||||
|         cart__active=True, |         cart__active=True, | ||||||
|     ).select_related( |     ).select_related( | ||||||
|  | @ -55,7 +56,7 @@ def items_purchased(context, category=None): | ||||||
|     ''' Returns all of the items that this user has purchased, optionally |     ''' Returns all of the items that this user has purchased, optionally | ||||||
|     from the given category. ''' |     from the given category. ''' | ||||||
| 
 | 
 | ||||||
|     all_items = rego.ProductItem.objects.filter( |     all_items = commerce.ProductItem.objects.filter( | ||||||
|         cart__user=context.request.user, |         cart__user=context.request.user, | ||||||
|         cart__active=False, |         cart__active=False, | ||||||
|         cart__released=False, |         cart__released=False, | ||||||
|  | @ -65,7 +66,7 @@ def items_purchased(context, category=None): | ||||||
|         all_items = all_items.filter(product__category=category) |         all_items = all_items.filter(product__category=category) | ||||||
| 
 | 
 | ||||||
|     pq = all_items.values("product").annotate(quantity=Sum("quantity")).all() |     pq = all_items.values("product").annotate(quantity=Sum("quantity")).all() | ||||||
|     products = rego.Product.objects.all() |     products = inventory.Product.objects.all() | ||||||
|     out = [] |     out = [] | ||||||
|     for item in pq: |     for item in pq: | ||||||
|         prod = products.get(pk=item["product"]) |         prod = products.get(pk=item["product"]) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| from registrasion.controllers.cart import CartController | from registrasion.controllers.cart import CartController | ||||||
| from registrasion.controllers.credit_note import CreditNoteController | from registrasion.controllers.credit_note import CreditNoteController | ||||||
| from registrasion.controllers.invoice import InvoiceController | from registrasion.controllers.invoice import InvoiceController | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
| 
 | 
 | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| 
 | 
 | ||||||
|  | @ -19,7 +19,7 @@ class TestingCartController(CartController): | ||||||
|         ValidationError if constraints are violated.''' |         ValidationError if constraints are violated.''' | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             product_item = rego.ProductItem.objects.get( |             product_item = commerce.ProductItem.objects.get( | ||||||
|                 cart=self.cart, |                 cart=self.cart, | ||||||
|                 product=product) |                 product=product) | ||||||
|             old_quantity = product_item.quantity |             old_quantity = product_item.quantity | ||||||
|  | @ -41,7 +41,7 @@ class TestingInvoiceController(InvoiceController): | ||||||
|         self.validate_allowed_to_pay() |         self.validate_allowed_to_pay() | ||||||
| 
 | 
 | ||||||
|         ''' Adds a payment ''' |         ''' Adds a payment ''' | ||||||
|         rego.ManualPayment.objects.create( |         commerce.ManualPayment.objects.create( | ||||||
|             invoice=self.invoice, |             invoice=self.invoice, | ||||||
|             reference=reference, |             reference=reference, | ||||||
|             amount=amount, |             amount=amount, | ||||||
|  | @ -53,7 +53,7 @@ class TestingInvoiceController(InvoiceController): | ||||||
| class TestingCreditNoteController(CreditNoteController): | class TestingCreditNoteController(CreditNoteController): | ||||||
| 
 | 
 | ||||||
|     def refund(self): |     def refund(self): | ||||||
|         rego.CreditNoteRefund.objects.create( |         commerce.CreditNoteRefund.objects.create( | ||||||
|             parent=self.credit_note, |             parent=self.credit_note, | ||||||
|             reference="Whoops." |             reference="Whoops." | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,10 @@ from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
|  | from registrasion.models import people | ||||||
| from registrasion.controllers.product import ProductController | from registrasion.controllers.product import ProductController | ||||||
| 
 | 
 | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
|  | @ -36,24 +39,28 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
|             email='test2@example.com', |             email='test2@example.com', | ||||||
|             password='top_secret') |             password='top_secret') | ||||||
| 
 | 
 | ||||||
|         attendee1 = rego.Attendee.get_instance(cls.USER_1) |         attendee1 = people.Attendee.get_instance(cls.USER_1) | ||||||
|         attendee1.save() |         attendee1.save() | ||||||
|         profile1 = rego.AttendeeProfileBase.objects.create(attendee=attendee1) |         profile1 = people.AttendeeProfileBase.objects.create( | ||||||
|  |             attendee=attendee1, | ||||||
|  |         ) | ||||||
|         profile1.save() |         profile1.save() | ||||||
|         attendee2 = rego.Attendee.get_instance(cls.USER_2) |         attendee2 = people.Attendee.get_instance(cls.USER_2) | ||||||
|         attendee2.save() |         attendee2.save() | ||||||
|         profile2 = rego.AttendeeProfileBase.objects.create(attendee=attendee2) |         profile2 = people.AttendeeProfileBase.objects.create( | ||||||
|  |             attendee=attendee2, | ||||||
|  |         ) | ||||||
|         profile2.save() |         profile2.save() | ||||||
| 
 | 
 | ||||||
|         cls.RESERVATION = datetime.timedelta(hours=1) |         cls.RESERVATION = datetime.timedelta(hours=1) | ||||||
| 
 | 
 | ||||||
|         cls.categories = [] |         cls.categories = [] | ||||||
|         for i in xrange(2): |         for i in xrange(2): | ||||||
|             cat = rego.Category.objects.create( |             cat = inventory.Category.objects.create( | ||||||
|                 name="Category " + str(i + 1), |                 name="Category " + str(i + 1), | ||||||
|                 description="This is a test category", |                 description="This is a test category", | ||||||
|                 order=i, |                 order=i, | ||||||
|                 render_type=rego.Category.RENDER_TYPE_RADIO, |                 render_type=inventory.Category.RENDER_TYPE_RADIO, | ||||||
|                 required=False, |                 required=False, | ||||||
|             ) |             ) | ||||||
|             cat.save() |             cat.save() | ||||||
|  | @ -64,7 +71,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
| 
 | 
 | ||||||
|         cls.products = [] |         cls.products = [] | ||||||
|         for i in xrange(4): |         for i in xrange(4): | ||||||
|             prod = rego.Product.objects.create( |             prod = inventory.Product.objects.create( | ||||||
|                 name="Product " + str(i + 1), |                 name="Product " + str(i + 1), | ||||||
|                 description="This is a test product.", |                 description="This is a test product.", | ||||||
|                 category=cls.categories[i / 2],  # 2 products per category |                 category=cls.categories[i / 2],  # 2 products per category | ||||||
|  | @ -95,9 +102,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): |     def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): | ||||||
|         limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( |         limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create( | ||||||
|             description=name, |             description=name, | ||||||
|             condition=rego.FlagBase.DISABLE_IF_FALSE, |             condition=conditions.FlagBase.DISABLE_IF_FALSE, | ||||||
|             limit=limit, |             limit=limit, | ||||||
|             start_time=start_time, |             start_time=start_time, | ||||||
|             end_time=end_time |             end_time=end_time | ||||||
|  | @ -109,9 +116,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
|     @classmethod |     @classmethod | ||||||
|     def make_category_ceiling( |     def make_category_ceiling( | ||||||
|             cls, name, limit=None, start_time=None, end_time=None): |             cls, name, limit=None, start_time=None, end_time=None): | ||||||
|         limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( |         limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create( | ||||||
|             description=name, |             description=name, | ||||||
|             condition=rego.FlagBase.DISABLE_IF_FALSE, |             condition=conditions.FlagBase.DISABLE_IF_FALSE, | ||||||
|             limit=limit, |             limit=limit, | ||||||
|             start_time=start_time, |             start_time=start_time, | ||||||
|             end_time=end_time |             end_time=end_time | ||||||
|  | @ -124,14 +131,14 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
|     def make_discount_ceiling( |     def make_discount_ceiling( | ||||||
|             cls, name, limit=None, start_time=None, end_time=None, |             cls, name, limit=None, start_time=None, end_time=None, | ||||||
|             percentage=100): |             percentage=100): | ||||||
|         limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create( |         limit_ceiling = conditions.TimeOrStockLimitDiscount.objects.create( | ||||||
|             description=name, |             description=name, | ||||||
|             start_time=start_time, |             start_time=start_time, | ||||||
|             end_time=end_time, |             end_time=end_time, | ||||||
|             limit=limit, |             limit=limit, | ||||||
|         ) |         ) | ||||||
|         limit_ceiling.save() |         limit_ceiling.save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=limit_ceiling, |             discount=limit_ceiling, | ||||||
|             product=cls.PROD_1, |             product=cls.PROD_1, | ||||||
|             percentage=percentage, |             percentage=percentage, | ||||||
|  | @ -140,7 +147,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new_voucher(self, code="VOUCHER", limit=1): |     def new_voucher(self, code="VOUCHER", limit=1): | ||||||
|         voucher = rego.Voucher.objects.create( |         voucher = inventory.Voucher.objects.create( | ||||||
|             recipient="Voucher recipient", |             recipient="Voucher recipient", | ||||||
|             code=code, |             code=code, | ||||||
|             limit=limit, |             limit=limit, | ||||||
|  | @ -176,7 +183,7 @@ class BasicCartTests(RegistrationCartTestCase): | ||||||
|         current_cart.add_to_cart(self.PROD_1, 1) |         current_cart.add_to_cart(self.PROD_1, 1) | ||||||
| 
 | 
 | ||||||
|         # Count of products for a given user should be collapsed. |         # Count of products for a given user should be collapsed. | ||||||
|         items = rego.ProductItem.objects.filter( |         items = commerce.ProductItem.objects.filter( | ||||||
|             cart=current_cart.cart, |             cart=current_cart.cart, | ||||||
|             product=self.PROD_1) |             product=self.PROD_1) | ||||||
|         self.assertEqual(1, len(items)) |         self.assertEqual(1, len(items)) | ||||||
|  | @ -187,7 +194,7 @@ class BasicCartTests(RegistrationCartTestCase): | ||||||
|         current_cart = TestingCartController.for_user(self.USER_1) |         current_cart = TestingCartController.for_user(self.USER_1) | ||||||
| 
 | 
 | ||||||
|         def get_item(): |         def get_item(): | ||||||
|             return rego.ProductItem.objects.get( |             return commerce.ProductItem.objects.get( | ||||||
|                 cart=current_cart.cart, |                 cart=current_cart.cart, | ||||||
|                 product=self.PROD_1) |                 product=self.PROD_1) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ from django.core.exceptions import ValidationError | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
| from test_cart import RegistrationCartTestCase | from test_cart import RegistrationCartTestCase | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import conditions | ||||||
| 
 | 
 | ||||||
| UTC = pytz.timezone('UTC') | UTC = pytz.timezone('UTC') | ||||||
| 
 | 
 | ||||||
|  | @ -155,12 +155,12 @@ class CeilingsTestCases(RegistrationCartTestCase): | ||||||
|         self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50) |         self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50) | ||||||
|         voucher = self.new_voucher(code="VOUCHER") |         voucher = self.new_voucher(code="VOUCHER") | ||||||
| 
 | 
 | ||||||
|         discount = rego.VoucherDiscount.objects.create( |         discount = conditions.VoucherDiscount.objects.create( | ||||||
|             description="VOUCHER RECIPIENT", |             description="VOUCHER RECIPIENT", | ||||||
|             voucher=voucher, |             voucher=voucher, | ||||||
|         ) |         ) | ||||||
|         discount.save() |         discount.save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=self.PROD_1, |             product=self.PROD_1, | ||||||
|             percentage=100, |             percentage=100, | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import pytz | ||||||
| 
 | 
 | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import conditions | ||||||
| from registrasion.controllers import discount | from registrasion.controllers import discount | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
| 
 | 
 | ||||||
|  | @ -19,13 +19,13 @@ class DiscountTestCase(RegistrationCartTestCase): | ||||||
|             amount=Decimal(100), |             amount=Decimal(100), | ||||||
|             quantity=2, |             quantity=2, | ||||||
|             ): |             ): | ||||||
|         discount = rego.IncludedProductDiscount.objects.create( |         discount = conditions.IncludedProductDiscount.objects.create( | ||||||
|             description="PROD_1 includes PROD_2 " + str(amount) + "%", |             description="PROD_1 includes PROD_2 " + str(amount) + "%", | ||||||
|         ) |         ) | ||||||
|         discount.save() |         discount.save() | ||||||
|         discount.enabling_products.add(cls.PROD_1) |         discount.enabling_products.add(cls.PROD_1) | ||||||
|         discount.save() |         discount.save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=cls.PROD_2, |             product=cls.PROD_2, | ||||||
|             percentage=amount, |             percentage=amount, | ||||||
|  | @ -39,13 +39,13 @@ class DiscountTestCase(RegistrationCartTestCase): | ||||||
|             amount=Decimal(100), |             amount=Decimal(100), | ||||||
|             quantity=2, |             quantity=2, | ||||||
|             ): |             ): | ||||||
|         discount = rego.IncludedProductDiscount.objects.create( |         discount = conditions.IncludedProductDiscount.objects.create( | ||||||
|             description="PROD_1 includes CAT_2 " + str(amount) + "%", |             description="PROD_1 includes CAT_2 " + str(amount) + "%", | ||||||
|         ) |         ) | ||||||
|         discount.save() |         discount.save() | ||||||
|         discount.enabling_products.add(cls.PROD_1) |         discount.enabling_products.add(cls.PROD_1) | ||||||
|         discount.save() |         discount.save() | ||||||
|         rego.DiscountForCategory.objects.create( |         conditions.DiscountForCategory.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             category=cls.CAT_2, |             category=cls.CAT_2, | ||||||
|             percentage=amount, |             percentage=amount, | ||||||
|  | @ -59,20 +59,20 @@ class DiscountTestCase(RegistrationCartTestCase): | ||||||
|             amount=Decimal(100), |             amount=Decimal(100), | ||||||
|             quantity=2, |             quantity=2, | ||||||
|             ): |             ): | ||||||
|         discount = rego.IncludedProductDiscount.objects.create( |         discount = conditions.IncludedProductDiscount.objects.create( | ||||||
|             description="PROD_1 includes PROD_3 and PROD_4 " + |             description="PROD_1 includes PROD_3 and PROD_4 " + | ||||||
|                         str(amount) + "%", |                         str(amount) + "%", | ||||||
|         ) |         ) | ||||||
|         discount.save() |         discount.save() | ||||||
|         discount.enabling_products.add(cls.PROD_1) |         discount.enabling_products.add(cls.PROD_1) | ||||||
|         discount.save() |         discount.save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=cls.PROD_3, |             product=cls.PROD_3, | ||||||
|             percentage=amount, |             percentage=amount, | ||||||
|             quantity=quantity, |             quantity=quantity, | ||||||
|         ).save() |         ).save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=cls.PROD_4, |             product=cls.PROD_4, | ||||||
|             percentage=amount, |             percentage=amount, | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ import pytz | ||||||
| 
 | 
 | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
| from registrasion.controllers.category import CategoryController | from registrasion.controllers.category import CategoryController | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
| from registrasion.controllers.product import ProductController | from registrasion.controllers.product import ProductController | ||||||
|  | @ -15,10 +16,10 @@ UTC = pytz.timezone('UTC') | ||||||
| class FlagTestCases(RegistrationCartTestCase): | class FlagTestCases(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def add_product_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): |     def add_product_flag(cls, condition=conditions.FlagBase.ENABLE_IF_TRUE): | ||||||
|         ''' Adds a product flag condition: adding PROD_1 to a cart is |         ''' Adds a product flag condition: adding PROD_1 to a cart is | ||||||
|         predicated on adding PROD_2 beforehand. ''' |         predicated on adding PROD_2 beforehand. ''' | ||||||
|         flag = rego.ProductFlag.objects.create( |         flag = conditions.ProductFlag.objects.create( | ||||||
|             description="Product condition", |             description="Product condition", | ||||||
|             condition=condition, |             condition=condition, | ||||||
|         ) |         ) | ||||||
|  | @ -28,10 +29,13 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         flag.save() |         flag.save() | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def add_product_flag_on_category(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): |     def add_product_flag_on_category( | ||||||
|  |             cls, | ||||||
|  |             condition=conditions.FlagBase.ENABLE_IF_TRUE, | ||||||
|  |             ): | ||||||
|         ''' Adds a product flag condition that operates on a category: |         ''' Adds a product flag condition that operates on a category: | ||||||
|         adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' |         adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' | ||||||
|         flag = rego.ProductFlag.objects.create( |         flag = conditions.ProductFlag.objects.create( | ||||||
|             description="Product condition", |             description="Product condition", | ||||||
|             condition=condition, |             condition=condition, | ||||||
|         ) |         ) | ||||||
|  | @ -40,10 +44,10 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         flag.enabling_products.add(cls.PROD_3) |         flag.enabling_products.add(cls.PROD_3) | ||||||
|         flag.save() |         flag.save() | ||||||
| 
 | 
 | ||||||
|     def add_category_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): |     def add_category_flag(cls, condition=conditions.FlagBase.ENABLE_IF_TRUE): | ||||||
|         ''' Adds a category flag condition: adding PROD_1 to a cart is |         ''' Adds a category flag condition: adding PROD_1 to a cart is | ||||||
|         predicated on adding an item from CAT_2 beforehand.''' |         predicated on adding an item from CAT_2 beforehand.''' | ||||||
|         flag = rego.CategoryFlag.objects.create( |         flag = conditions.CategoryFlag.objects.create( | ||||||
|             description="Category condition", |             description="Category condition", | ||||||
|             condition=condition, |             condition=condition, | ||||||
|             enabling_category=cls.CAT_2, |             enabling_category=cls.CAT_2, | ||||||
|  | @ -131,8 +135,8 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         cart_2.add_to_cart(self.PROD_1, 1) |         cart_2.add_to_cart(self.PROD_1, 1) | ||||||
| 
 | 
 | ||||||
|     def test_multiple_dif_conditions(self): |     def test_multiple_dif_conditions(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) |         self.add_product_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE) | ||||||
|         self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) |         self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE) | ||||||
| 
 | 
 | ||||||
|         cart_1 = TestingCartController.for_user(self.USER_1) |         cart_1 = TestingCartController.for_user(self.USER_1) | ||||||
|         # Cannot add PROD_1 until both conditions are met |         # Cannot add PROD_1 until both conditions are met | ||||||
|  | @ -145,8 +149,8 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         cart_1.add_to_cart(self.PROD_1, 1) |         cart_1.add_to_cart(self.PROD_1, 1) | ||||||
| 
 | 
 | ||||||
|     def test_eit_and_dif_conditions_work_together(self): |     def test_eit_and_dif_conditions_work_together(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
|         self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) |         self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE) | ||||||
| 
 | 
 | ||||||
|         cart_1 = TestingCartController.for_user(self.USER_1) |         cart_1 = TestingCartController.for_user(self.USER_1) | ||||||
|         # Cannot add PROD_1 until both conditions are met |         # Cannot add PROD_1 until both conditions are met | ||||||
|  | @ -200,7 +204,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.PROD_4 in prods) |         self.assertTrue(self.PROD_4 in prods) | ||||||
| 
 | 
 | ||||||
|     def test_available_products_on_category_works_when_condition_not_met(self): |     def test_available_products_on_category_works_when_condition_not_met(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         prods = ProductController.available_products( |         prods = ProductController.available_products( | ||||||
|             self.USER_1, |             self.USER_1, | ||||||
|  | @ -211,7 +215,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.PROD_2 in prods) |         self.assertTrue(self.PROD_2 in prods) | ||||||
| 
 | 
 | ||||||
|     def test_available_products_on_category_works_when_condition_is_met(self): |     def test_available_products_on_category_works_when_condition_is_met(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         cart_1 = TestingCartController.for_user(self.USER_1) |         cart_1 = TestingCartController.for_user(self.USER_1) | ||||||
|         cart_1.add_to_cart(self.PROD_2, 1) |         cart_1.add_to_cart(self.PROD_2, 1) | ||||||
|  | @ -225,7 +229,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.PROD_2 in prods) |         self.assertTrue(self.PROD_2 in prods) | ||||||
| 
 | 
 | ||||||
|     def test_available_products_on_products_works_when_condition_not_met(self): |     def test_available_products_on_products_works_when_condition_not_met(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         prods = ProductController.available_products( |         prods = ProductController.available_products( | ||||||
|             self.USER_1, |             self.USER_1, | ||||||
|  | @ -236,7 +240,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.PROD_2 in prods) |         self.assertTrue(self.PROD_2 in prods) | ||||||
| 
 | 
 | ||||||
|     def test_available_products_on_products_works_when_condition_is_met(self): |     def test_available_products_on_products_works_when_condition_is_met(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         cart_1 = TestingCartController.for_user(self.USER_1) |         cart_1 = TestingCartController.for_user(self.USER_1) | ||||||
|         cart_1.add_to_cart(self.PROD_2, 1) |         cart_1.add_to_cart(self.PROD_2, 1) | ||||||
|  | @ -250,7 +254,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.PROD_2 in prods) |         self.assertTrue(self.PROD_2 in prods) | ||||||
| 
 | 
 | ||||||
|     def test_category_flag_fails_if_cart_refunded(self): |     def test_category_flag_fails_if_cart_refunded(self): | ||||||
|         self.add_category_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_category_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|         cart.add_to_cart(self.PROD_3, 1) |         cart.add_to_cart(self.PROD_3, 1) | ||||||
|  | @ -268,7 +272,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|             cart_2.set_quantity(self.PROD_1, 1) |             cart_2.set_quantity(self.PROD_1, 1) | ||||||
| 
 | 
 | ||||||
|     def test_product_flag_fails_if_cart_refunded(self): |     def test_product_flag_fails_if_cart_refunded(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|         cart.add_to_cart(self.PROD_2, 1) |         cart.add_to_cart(self.PROD_2, 1) | ||||||
|  | @ -286,7 +290,9 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|             cart_2.set_quantity(self.PROD_1, 1) |             cart_2.set_quantity(self.PROD_1, 1) | ||||||
| 
 | 
 | ||||||
|     def test_available_categories(self): |     def test_available_categories(self): | ||||||
|         self.add_product_flag_on_category(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag_on_category( | ||||||
|  |             condition=conditions.FlagBase.ENABLE_IF_TRUE, | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         cart_1 = TestingCartController.for_user(self.USER_1) |         cart_1 = TestingCartController.for_user(self.USER_1) | ||||||
| 
 | 
 | ||||||
|  | @ -307,7 +313,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         self.assertTrue(self.CAT_2 in cats) |         self.assertTrue(self.CAT_2 in cats) | ||||||
| 
 | 
 | ||||||
|     def test_validate_cart_when_flags_become_unmet(self): |     def test_validate_cart_when_flags_become_unmet(self): | ||||||
|         self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) |         self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE) | ||||||
| 
 | 
 | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|         cart.add_to_cart(self.PROD_2, 1) |         cart.add_to_cart(self.PROD_2, 1) | ||||||
|  | @ -332,7 +338,7 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
|         cart.validate_cart() |         cart.validate_cart() | ||||||
| 
 | 
 | ||||||
|         # Should keep PROD_2 in the cart |         # Should keep PROD_2 in the cart | ||||||
|         items = rego.ProductItem.objects.filter(cart=cart.cart) |         items = commerce.ProductItem.objects.filter(cart=cart.cart) | ||||||
|         self.assertFalse([i for i in items if i.product == self.PROD_1]) |         self.assertFalse([i for i in items if i.product == self.PROD_1]) | ||||||
| 
 | 
 | ||||||
|     def test_fix_simple_errors_does_not_remove_limited_items(self): |     def test_fix_simple_errors_does_not_remove_limited_items(self): | ||||||
|  | @ -348,5 +354,5 @@ class FlagTestCases(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|         # Should keep PROD_2 in the cart |         # Should keep PROD_2 in the cart | ||||||
|         # and also PROD_1, which is now exhausted for user. |         # and also PROD_1, which is now exhausted for user. | ||||||
|         items = rego.ProductItem.objects.filter(cart=cart.cart) |         items = commerce.ProductItem.objects.filter(cart=cart.cart) | ||||||
|         self.assertTrue([i for i in items if i.product == self.PROD_1]) |         self.assertTrue([i for i in items if i.product == self.PROD_1]) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,9 @@ import pytz | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
| from controller_helpers import TestingCreditNoteController | from controller_helpers import TestingCreditNoteController | ||||||
| from controller_helpers import TestingInvoiceController | from controller_helpers import TestingInvoiceController | ||||||
|  | @ -23,7 +25,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         current_cart.add_to_cart(self.PROD_1, 1) |         current_cart.add_to_cart(self.PROD_1, 1) | ||||||
|         invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) |         invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) | ||||||
|         # That invoice should have a single line item |         # That invoice should have a single line item | ||||||
|         line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice) |         line_items = commerce.LineItem.objects.filter( | ||||||
|  |             invoice=invoice_1.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(1, len(line_items)) |         self.assertEqual(1, len(line_items)) | ||||||
|         # That invoice should have a value equal to cost of PROD_1 |         # That invoice should have a value equal to cost of PROD_1 | ||||||
|         self.assertEqual(self.PROD_1.price, invoice_1.invoice.value) |         self.assertEqual(self.PROD_1.price, invoice_1.invoice.value) | ||||||
|  | @ -34,13 +38,15 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertNotEqual(invoice_1.invoice, invoice_2.invoice) |         self.assertNotEqual(invoice_1.invoice, invoice_2.invoice) | ||||||
| 
 | 
 | ||||||
|         # The old invoice should automatically be voided |         # The old invoice should automatically be voided | ||||||
|         invoice_1_new = rego.Invoice.objects.get(pk=invoice_1.invoice.id) |         invoice_1_new = commerce.Invoice.objects.get(pk=invoice_1.invoice.id) | ||||||
|         invoice_2_new = rego.Invoice.objects.get(pk=invoice_2.invoice.id) |         invoice_2_new = commerce.Invoice.objects.get(pk=invoice_2.invoice.id) | ||||||
|         self.assertTrue(invoice_1_new.is_void) |         self.assertTrue(invoice_1_new.is_void) | ||||||
|         self.assertFalse(invoice_2_new.is_void) |         self.assertFalse(invoice_2_new.is_void) | ||||||
| 
 | 
 | ||||||
|         # Invoice should have two line items |         # Invoice should have two line items | ||||||
|         line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice) |         line_items = commerce.LineItem.objects.filter( | ||||||
|  |             invoice=invoice_2.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(2, len(line_items)) |         self.assertEqual(2, len(line_items)) | ||||||
|         # Invoice should have a value equal to cost of PROD_1 and PROD_2 |         # Invoice should have a value equal to cost of PROD_1 and PROD_2 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|  | @ -79,16 +85,16 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertNotEqual(current_cart.cart, new_cart.cart) |         self.assertNotEqual(current_cart.cart, new_cart.cart) | ||||||
| 
 | 
 | ||||||
|     def test_invoice_includes_discounts(self): |     def test_invoice_includes_discounts(self): | ||||||
|         voucher = rego.Voucher.objects.create( |         voucher = inventory.Voucher.objects.create( | ||||||
|             recipient="Voucher recipient", |             recipient="Voucher recipient", | ||||||
|             code="VOUCHER", |             code="VOUCHER", | ||||||
|             limit=1 |             limit=1 | ||||||
|         ) |         ) | ||||||
|         discount = rego.VoucherDiscount.objects.create( |         discount = conditions.VoucherDiscount.objects.create( | ||||||
|             description="VOUCHER RECIPIENT", |             description="VOUCHER RECIPIENT", | ||||||
|             voucher=voucher, |             voucher=voucher, | ||||||
|         ) |         ) | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=self.PROD_1, |             product=self.PROD_1, | ||||||
|             percentage=Decimal(50), |             percentage=Decimal(50), | ||||||
|  | @ -103,7 +109,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) |         invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) | ||||||
| 
 | 
 | ||||||
|         # That invoice should have two line items |         # That invoice should have two line items | ||||||
|         line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice) |         line_items = commerce.LineItem.objects.filter( | ||||||
|  |             invoice=invoice_1.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(2, len(line_items)) |         self.assertEqual(2, len(line_items)) | ||||||
|         # That invoice should have a value equal to 50% of the cost of PROD_1 |         # That invoice should have a value equal to 50% of the cost of PROD_1 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|  | @ -111,16 +119,16 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|             invoice_1.invoice.value) |             invoice_1.invoice.value) | ||||||
| 
 | 
 | ||||||
|     def test_zero_value_invoice_is_automatically_paid(self): |     def test_zero_value_invoice_is_automatically_paid(self): | ||||||
|         voucher = rego.Voucher.objects.create( |         voucher = inventory.Voucher.objects.create( | ||||||
|             recipient="Voucher recipient", |             recipient="Voucher recipient", | ||||||
|             code="VOUCHER", |             code="VOUCHER", | ||||||
|             limit=1 |             limit=1 | ||||||
|         ) |         ) | ||||||
|         discount = rego.VoucherDiscount.objects.create( |         discount = conditions.VoucherDiscount.objects.create( | ||||||
|             description="VOUCHER RECIPIENT", |             description="VOUCHER RECIPIENT", | ||||||
|             voucher=voucher, |             voucher=voucher, | ||||||
|         ) |         ) | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=self.PROD_1, |             product=self.PROD_1, | ||||||
|             percentage=Decimal(100), |             percentage=Decimal(100), | ||||||
|  | @ -239,7 +247,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertTrue(invoice.invoice.is_paid) |         self.assertTrue(invoice.invoice.is_paid) | ||||||
| 
 | 
 | ||||||
|         # There should be a credit note generated out of the invoice. |         # There should be a credit note generated out of the invoice. | ||||||
|         credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice) |         credit_notes = commerce.CreditNote.objects.filter( | ||||||
|  |             invoice=invoice.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(1, credit_notes.count()) |         self.assertEqual(1, credit_notes.count()) | ||||||
|         self.assertEqual(to_pay - invoice.invoice.value, credit_notes[0].value) |         self.assertEqual(to_pay - invoice.invoice.value, credit_notes[0].value) | ||||||
| 
 | 
 | ||||||
|  | @ -257,7 +267,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertTrue(invoice.invoice.is_paid) |         self.assertTrue(invoice.invoice.is_paid) | ||||||
| 
 | 
 | ||||||
|         # There should be no credit notes |         # There should be no credit notes | ||||||
|         credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice) |         credit_notes = commerce.CreditNote.objects.filter( | ||||||
|  |             invoice=invoice.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(0, credit_notes.count()) |         self.assertEqual(0, credit_notes.count()) | ||||||
| 
 | 
 | ||||||
|     def test_refund_partially_paid_invoice_generates_correct_credit_note(self): |     def test_refund_partially_paid_invoice_generates_correct_credit_note(self): | ||||||
|  | @ -276,7 +288,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertTrue(invoice.invoice.is_void) |         self.assertTrue(invoice.invoice.is_void) | ||||||
| 
 | 
 | ||||||
|         # There should be a credit note generated out of the invoice. |         # There should be a credit note generated out of the invoice. | ||||||
|         credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice) |         credit_notes = commerce.CreditNote.objects.filter( | ||||||
|  |             invoice=invoice.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(1, credit_notes.count()) |         self.assertEqual(1, credit_notes.count()) | ||||||
|         self.assertEqual(to_pay, credit_notes[0].value) |         self.assertEqual(to_pay, credit_notes[0].value) | ||||||
| 
 | 
 | ||||||
|  | @ -297,7 +311,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertTrue(invoice.invoice.is_refunded) |         self.assertTrue(invoice.invoice.is_refunded) | ||||||
| 
 | 
 | ||||||
|         # There should be a credit note generated out of the invoice. |         # There should be a credit note generated out of the invoice. | ||||||
|         credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice) |         credit_notes = commerce.CreditNote.objects.filter( | ||||||
|  |             invoice=invoice.invoice, | ||||||
|  |         ) | ||||||
|         self.assertEqual(1, credit_notes.count()) |         self.assertEqual(1, credit_notes.count()) | ||||||
|         self.assertEqual(to_pay, credit_notes[0].value) |         self.assertEqual(to_pay, credit_notes[0].value) | ||||||
| 
 | 
 | ||||||
|  | @ -314,11 +330,11 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         invoice.refund() |         invoice.refund() | ||||||
| 
 | 
 | ||||||
|         # There should be one credit note generated out of the invoice. |         # There should be one credit note generated out of the invoice. | ||||||
|         credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice) |         credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) | ||||||
|         cn = TestingCreditNoteController(credit_note) |         cn = TestingCreditNoteController(credit_note) | ||||||
| 
 | 
 | ||||||
|         # That credit note should be in the unclaimed pile |         # That credit note should be in the unclaimed pile | ||||||
|         self.assertEquals(1, rego.CreditNote.unclaimed().count()) |         self.assertEquals(1, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         # Create a new (identical) cart with invoice |         # Create a new (identical) cart with invoice | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|  | @ -330,7 +346,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         self.assertTrue(invoice2.invoice.is_paid) |         self.assertTrue(invoice2.invoice.is_paid) | ||||||
| 
 | 
 | ||||||
|         # That invoice should not show up as unclaimed any more |         # That invoice should not show up as unclaimed any more | ||||||
|         self.assertEquals(0, rego.CreditNote.unclaimed().count()) |         self.assertEquals(0, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|     def test_apply_credit_note_generates_new_credit_note_if_overpaying(self): |     def test_apply_credit_note_generates_new_credit_note_if_overpaying(self): | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|  | @ -345,10 +361,10 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         invoice.refund() |         invoice.refund() | ||||||
| 
 | 
 | ||||||
|         # There should be one credit note generated out of the invoice. |         # There should be one credit note generated out of the invoice. | ||||||
|         credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice) |         credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) | ||||||
|         cn = TestingCreditNoteController(credit_note) |         cn = TestingCreditNoteController(credit_note) | ||||||
| 
 | 
 | ||||||
|         self.assertEquals(1, rego.CreditNote.unclaimed().count()) |         self.assertEquals(1, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         # Create a new cart (of half value of inv 1) and get invoice |         # Create a new cart (of half value of inv 1) and get invoice | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|  | @ -361,9 +377,11 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|         # We generated a new credit note, and spent the old one, |         # We generated a new credit note, and spent the old one, | ||||||
|         # unclaimed should still be 1. |         # unclaimed should still be 1. | ||||||
|         self.assertEquals(1, rego.CreditNote.unclaimed().count()) |         self.assertEquals(1, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         credit_note2 = rego.CreditNote.objects.get(invoice=invoice2.invoice) |         credit_note2 = commerce.CreditNote.objects.get( | ||||||
|  |             invoice=invoice2.invoice, | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         # The new credit note should be the residual of the cost of cart 1 |         # The new credit note should be the residual of the cost of cart 1 | ||||||
|         # minus the cost of cart 2. |         # minus the cost of cart 2. | ||||||
|  | @ -385,7 +403,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         invoice.refund() |         invoice.refund() | ||||||
| 
 | 
 | ||||||
|         # There should be one credit note generated out of the invoice. |         # There should be one credit note generated out of the invoice. | ||||||
|         credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice) |         credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) | ||||||
|         cn = TestingCreditNoteController(credit_note) |         cn = TestingCreditNoteController(credit_note) | ||||||
| 
 | 
 | ||||||
|         # Create a new cart with invoice, pay it |         # Create a new cart with invoice, pay it | ||||||
|  | @ -426,15 +444,15 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|         invoice.refund() |         invoice.refund() | ||||||
| 
 | 
 | ||||||
|         self.assertEquals(1, rego.CreditNote.unclaimed().count()) |         self.assertEquals(1, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice) |         credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) | ||||||
| 
 | 
 | ||||||
|         cn = TestingCreditNoteController(credit_note) |         cn = TestingCreditNoteController(credit_note) | ||||||
|         cn.refund() |         cn.refund() | ||||||
| 
 | 
 | ||||||
|         # Refunding a credit note should mark it as claimed |         # Refunding a credit note should mark it as claimed | ||||||
|         self.assertEquals(0, rego.CreditNote.unclaimed().count()) |         self.assertEquals(0, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         # Create a new cart with invoice |         # Create a new cart with invoice | ||||||
|         cart = TestingCartController.for_user(self.USER_1) |         cart = TestingCartController.for_user(self.USER_1) | ||||||
|  | @ -458,9 +476,9 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|         invoice.refund() |         invoice.refund() | ||||||
| 
 | 
 | ||||||
|         self.assertEquals(1, rego.CreditNote.unclaimed().count()) |         self.assertEquals(1, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice) |         credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) | ||||||
| 
 | 
 | ||||||
|         cn = TestingCreditNoteController(credit_note) |         cn = TestingCreditNoteController(credit_note) | ||||||
| 
 | 
 | ||||||
|  | @ -471,7 +489,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | ||||||
|         invoice_2 = TestingInvoiceController.for_cart(self.reget(cart.cart)) |         invoice_2 = TestingInvoiceController.for_cart(self.reget(cart.cart)) | ||||||
|         cn.apply_to_invoice(invoice_2.invoice) |         cn.apply_to_invoice(invoice_2.invoice) | ||||||
| 
 | 
 | ||||||
|         self.assertEquals(0, rego.CreditNote.unclaimed().count()) |         self.assertEquals(0, commerce.CreditNote.unclaimed().count()) | ||||||
| 
 | 
 | ||||||
|         # Cannot refund this credit note as it is already applied. |         # Cannot refund this credit note as it is already applied. | ||||||
|         with self.assertRaises(ValidationError): |         with self.assertRaises(ValidationError): | ||||||
|  |  | ||||||
|  | @ -5,7 +5,8 @@ from decimal import Decimal | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| from django.db import IntegrityError | from django.db import IntegrityError | ||||||
| 
 | 
 | ||||||
| from registrasion import models as rego | from registrasion.models import conditions | ||||||
|  | from registrasion.models import inventory | ||||||
| from controller_helpers import TestingCartController | from controller_helpers import TestingCartController | ||||||
| from controller_helpers import TestingInvoiceController | from controller_helpers import TestingInvoiceController | ||||||
| 
 | 
 | ||||||
|  | @ -32,14 +33,14 @@ class VoucherTestCases(RegistrationCartTestCase): | ||||||
| 
 | 
 | ||||||
|         # After the reservation duration |         # After the reservation duration | ||||||
|         # user 2 should be able to apply voucher |         # user 2 should be able to apply voucher | ||||||
|         self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2) |         self.add_timedelta(inventory.Voucher.RESERVATION_DURATION * 2) | ||||||
|         cart_2.apply_voucher(voucher.code) |         cart_2.apply_voucher(voucher.code) | ||||||
| 
 | 
 | ||||||
|         cart_2.next_cart() |         cart_2.next_cart() | ||||||
| 
 | 
 | ||||||
|         # After the reservation duration, even though the voucher has applied, |         # After the reservation duration, even though the voucher has applied, | ||||||
|         # it exceeds the number of vouchers available. |         # it exceeds the number of vouchers available. | ||||||
|         self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2) |         self.add_timedelta(inventory.Voucher.RESERVATION_DURATION * 2) | ||||||
|         with self.assertRaises(ValidationError): |         with self.assertRaises(ValidationError): | ||||||
|             cart_1.validate_cart() |             cart_1.validate_cart() | ||||||
| 
 | 
 | ||||||
|  | @ -58,10 +59,10 @@ class VoucherTestCases(RegistrationCartTestCase): | ||||||
|     def test_voucher_enables_item(self): |     def test_voucher_enables_item(self): | ||||||
|         voucher = self.new_voucher() |         voucher = self.new_voucher() | ||||||
| 
 | 
 | ||||||
|         flag = rego.VoucherFlag.objects.create( |         flag = conditions.VoucherFlag.objects.create( | ||||||
|             description="Voucher condition", |             description="Voucher condition", | ||||||
|             voucher=voucher, |             voucher=voucher, | ||||||
|             condition=rego.FlagBase.ENABLE_IF_TRUE, |             condition=conditions.FlagBase.ENABLE_IF_TRUE, | ||||||
|         ) |         ) | ||||||
|         flag.save() |         flag.save() | ||||||
|         flag.products.add(self.PROD_1) |         flag.products.add(self.PROD_1) | ||||||
|  | @ -79,12 +80,12 @@ class VoucherTestCases(RegistrationCartTestCase): | ||||||
|     def test_voucher_enables_discount(self): |     def test_voucher_enables_discount(self): | ||||||
|         voucher = self.new_voucher() |         voucher = self.new_voucher() | ||||||
| 
 | 
 | ||||||
|         discount = rego.VoucherDiscount.objects.create( |         discount = conditions.VoucherDiscount.objects.create( | ||||||
|             description="VOUCHER RECIPIENT", |             description="VOUCHER RECIPIENT", | ||||||
|             voucher=voucher, |             voucher=voucher, | ||||||
|         ) |         ) | ||||||
|         discount.save() |         discount.save() | ||||||
|         rego.DiscountForProduct.objects.create( |         conditions.DiscountForProduct.objects.create( | ||||||
|             discount=discount, |             discount=discount, | ||||||
|             product=self.PROD_1, |             product=self.PROD_1, | ||||||
|             percentage=Decimal(100), |             percentage=Decimal(100), | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| from registrasion import forms | from registrasion import forms | ||||||
| from registrasion import models as rego | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
|  | from registrasion.models import people | ||||||
| from registrasion.controllers import discount | from registrasion.controllers import discount | ||||||
| from registrasion.controllers.cart import CartController | from registrasion.controllers.cart import CartController | ||||||
| from registrasion.controllers.credit_note import CreditNoteController | from registrasion.controllers.credit_note import CreditNoteController | ||||||
|  | @ -60,7 +62,7 @@ def guided_registration(request, page_id=0): | ||||||
| 
 | 
 | ||||||
|     sections = [] |     sections = [] | ||||||
| 
 | 
 | ||||||
|     attendee = rego.Attendee.get_instance(request.user) |     attendee = people.Attendee.get_instance(request.user) | ||||||
| 
 | 
 | ||||||
|     if attendee.completed_registration: |     if attendee.completed_registration: | ||||||
|         return render( |         return render( | ||||||
|  | @ -111,7 +113,7 @@ def guided_registration(request, page_id=0): | ||||||
|         starting = attendee.guided_categories_complete.count() == 0 |         starting = attendee.guided_categories_complete.count() == 0 | ||||||
| 
 | 
 | ||||||
|         # Get the next category |         # Get the next category | ||||||
|         cats = rego.Category.objects |         cats = inventory.Category.objects | ||||||
|         if SESSION_KEY in request.session: |         if SESSION_KEY in request.session: | ||||||
|             _cats = request.session[SESSION_KEY] |             _cats = request.session[SESSION_KEY] | ||||||
|             cats = cats.filter(id__in=_cats) |             cats = cats.filter(id__in=_cats) | ||||||
|  | @ -134,7 +136,7 @@ def guided_registration(request, page_id=0): | ||||||
|             current_step = 3 |             current_step = 3 | ||||||
|             title = "Additional items" |             title = "Additional items" | ||||||
| 
 | 
 | ||||||
|         all_products = rego.Product.objects.filter( |         all_products = inventory.Product.objects.filter( | ||||||
|             category__in=cats, |             category__in=cats, | ||||||
|         ).select_related("category") |         ).select_related("category") | ||||||
| 
 | 
 | ||||||
|  | @ -217,11 +219,13 @@ def edit_profile(request): | ||||||
| def handle_profile(request, prefix): | def handle_profile(request, prefix): | ||||||
|     ''' Returns a profile form instance, and a boolean which is true if the |     ''' Returns a profile form instance, and a boolean which is true if the | ||||||
|     form was handled. ''' |     form was handled. ''' | ||||||
|     attendee = rego.Attendee.get_instance(request.user) |     attendee = people.Attendee.get_instance(request.user) | ||||||
| 
 | 
 | ||||||
|     try: |     try: | ||||||
|         profile = attendee.attendeeprofilebase |         profile = attendee.attendeeprofilebase | ||||||
|         profile = rego.AttendeeProfileBase.objects.get_subclass(pk=profile.id) |         profile = people.AttendeeProfileBase.objects.get_subclass( | ||||||
|  |             pk=profile.id, | ||||||
|  |         ) | ||||||
|     except ObjectDoesNotExist: |     except ObjectDoesNotExist: | ||||||
|         profile = None |         profile = None | ||||||
| 
 | 
 | ||||||
|  | @ -270,7 +274,7 @@ def product_category(request, category_id): | ||||||
|     voucher_form, voucher_handled = v |     voucher_form, voucher_handled = v | ||||||
| 
 | 
 | ||||||
|     category_id = int(category_id)  # Routing is [0-9]+ |     category_id = int(category_id)  # Routing is [0-9]+ | ||||||
|     category = rego.Category.objects.get(pk=category_id) |     category = inventory.Category.objects.get(pk=category_id) | ||||||
| 
 | 
 | ||||||
|     products = ProductController.available_products( |     products = ProductController.available_products( | ||||||
|         request.user, |         request.user, | ||||||
|  | @ -316,7 +320,7 @@ def handle_products(request, category, products, prefix): | ||||||
|     ProductsForm = forms.ProductsForm(category, products) |     ProductsForm = forms.ProductsForm(category, products) | ||||||
| 
 | 
 | ||||||
|     # Create initial data for each of products in category |     # Create initial data for each of products in category | ||||||
|     items = rego.ProductItem.objects.filter( |     items = commerce.ProductItem.objects.filter( | ||||||
|         product__in=products, |         product__in=products, | ||||||
|         cart=current_cart.cart, |         cart=current_cart.cart, | ||||||
|     ).select_related("product") |     ).select_related("product") | ||||||
|  | @ -344,8 +348,8 @@ def handle_products(request, category, products, prefix): | ||||||
|         # If category is required, the user must have at least one |         # If category is required, the user must have at least one | ||||||
|         # in an active+valid cart |         # in an active+valid cart | ||||||
|         if category.required: |         if category.required: | ||||||
|             carts = rego.Cart.objects.filter(user=request.user) |             carts = commerce.Cart.objects.filter(user=request.user) | ||||||
|             items = rego.ProductItem.objects.filter( |             items = commerce.ProductItem.objects.filter( | ||||||
|                 product__category=category, |                 product__category=category, | ||||||
|                 cart=carts, |                 cart=carts, | ||||||
|             ) |             ) | ||||||
|  | @ -366,7 +370,7 @@ def set_quantities_from_products_form(products_form, current_cart): | ||||||
|     quantities = list(products_form.product_quantities()) |     quantities = list(products_form.product_quantities()) | ||||||
| 
 | 
 | ||||||
|     pks = [i[0] for i in quantities] |     pks = [i[0] for i in quantities] | ||||||
|     products = rego.Product.objects.filter( |     products = inventory.Product.objects.filter( | ||||||
|         id__in=pks, |         id__in=pks, | ||||||
|     ).select_related("category") |     ).select_related("category") | ||||||
| 
 | 
 | ||||||
|  | @ -384,7 +388,7 @@ def set_quantities_from_products_form(products_form, current_cart): | ||||||
|             product, message = ve_field.message |             product, message = ve_field.message | ||||||
|             if product in field_names: |             if product in field_names: | ||||||
|                 field = field_names[product] |                 field = field_names[product] | ||||||
|             elif isinstance(product, rego.Product): |             elif isinstance(product, inventory.Product): | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 field = None |                 field = None | ||||||
|  | @ -402,7 +406,7 @@ def handle_voucher(request, prefix): | ||||||
|             voucher_form.cleaned_data["voucher"].strip()): |             voucher_form.cleaned_data["voucher"].strip()): | ||||||
| 
 | 
 | ||||||
|         voucher = voucher_form.cleaned_data["voucher"] |         voucher = voucher_form.cleaned_data["voucher"] | ||||||
|         voucher = rego.Voucher.normalise_code(voucher) |         voucher = inventory.Voucher.normalise_code(voucher) | ||||||
| 
 | 
 | ||||||
|         if len(current_cart.cart.vouchers.filter(code=voucher)) > 0: |         if len(current_cart.cart.vouchers.filter(code=voucher)) > 0: | ||||||
|             # This voucher has already been applied to this cart. |             # This voucher has already been applied to this cart. | ||||||
|  | @ -457,9 +461,9 @@ def invoice_access(request, access_code): | ||||||
|     ''' Redirects to the first unpaid invoice for the attendee that matches |     ''' Redirects to the first unpaid invoice for the attendee that matches | ||||||
|     the given access code, if any. ''' |     the given access code, if any. ''' | ||||||
| 
 | 
 | ||||||
|     invoices = rego.Invoice.objects.filter( |     invoices = commerce.Invoice.objects.filter( | ||||||
|         user__attendee__access_code=access_code, |         user__attendee__access_code=access_code, | ||||||
|         status=rego.Invoice.STATUS_UNPAID, |         status=commerce.Invoice.STATUS_UNPAID, | ||||||
|     ).order_by("issue_time") |     ).order_by("issue_time") | ||||||
| 
 | 
 | ||||||
|     if not invoices: |     if not invoices: | ||||||
|  | @ -478,7 +482,7 @@ def invoice(request, invoice_id, access_code=None): | ||||||
|     ''' |     ''' | ||||||
| 
 | 
 | ||||||
|     invoice_id = int(invoice_id) |     invoice_id = int(invoice_id) | ||||||
|     inv = rego.Invoice.objects.get(pk=invoice_id) |     inv = commerce.Invoice.objects.get(pk=invoice_id) | ||||||
| 
 | 
 | ||||||
|     current_invoice = InvoiceController(inv) |     current_invoice = InvoiceController(inv) | ||||||
| 
 | 
 | ||||||
|  | @ -505,7 +509,7 @@ def manual_payment(request, invoice_id): | ||||||
|         raise Http404() |         raise Http404() | ||||||
| 
 | 
 | ||||||
|     invoice_id = int(invoice_id) |     invoice_id = int(invoice_id) | ||||||
|     inv = get_object_or_404(rego.Invoice, pk=invoice_id) |     inv = get_object_or_404(commerce.Invoice, pk=invoice_id) | ||||||
|     current_invoice = InvoiceController(inv) |     current_invoice = InvoiceController(inv) | ||||||
| 
 | 
 | ||||||
|     form = forms.ManualPaymentForm( |     form = forms.ManualPaymentForm( | ||||||
|  | @ -536,7 +540,7 @@ def refund(request, invoice_id): | ||||||
|         raise Http404() |         raise Http404() | ||||||
| 
 | 
 | ||||||
|     invoice_id = int(invoice_id) |     invoice_id = int(invoice_id) | ||||||
|     inv = get_object_or_404(rego.Invoice, pk=invoice_id) |     inv = get_object_or_404(commerce.Invoice, pk=invoice_id) | ||||||
|     current_invoice = InvoiceController(inv) |     current_invoice = InvoiceController(inv) | ||||||
| 
 | 
 | ||||||
|     try: |     try: | ||||||
|  | @ -557,7 +561,7 @@ def credit_note(request, note_id, access_code=None): | ||||||
|         raise Http404() |         raise Http404() | ||||||
| 
 | 
 | ||||||
|     note_id = int(note_id) |     note_id = int(note_id) | ||||||
|     note = rego.CreditNote.objects.get(pk=note_id) |     note = commerce.CreditNote.objects.get(pk=note_id) | ||||||
| 
 | 
 | ||||||
|     current_note = CreditNoteController(note) |     current_note = CreditNoteController(note) | ||||||
| 
 | 
 | ||||||
|  | @ -574,10 +578,11 @@ def credit_note(request, note_id, access_code=None): | ||||||
| 
 | 
 | ||||||
|     if request.POST and apply_form.is_valid(): |     if request.POST and apply_form.is_valid(): | ||||||
|         inv_id = apply_form.cleaned_data["invoice"] |         inv_id = apply_form.cleaned_data["invoice"] | ||||||
|         invoice = rego.Invoice.objects.get(pk=inv_id) |         invoice = commerce.Invoice.objects.get(pk=inv_id) | ||||||
|         current_note.apply_to_invoice(invoice) |         current_note.apply_to_invoice(invoice) | ||||||
|         messages.success(request, |         messages.success( | ||||||
|             "Applied credit note %d to invoice." % note_id |             request, | ||||||
|  |             "Applied credit note %d to invoice." % note_id, | ||||||
|         ) |         ) | ||||||
|         return redirect("invoice", invoice.id) |         return redirect("invoice", invoice.id) | ||||||
| 
 | 
 | ||||||
|  | @ -585,7 +590,8 @@ def credit_note(request, note_id, access_code=None): | ||||||
|         refund_form.instance.entered_by = request.user |         refund_form.instance.entered_by = request.user | ||||||
|         refund_form.instance.parent = note |         refund_form.instance.parent = note | ||||||
|         refund_form.save() |         refund_form.save() | ||||||
|         messages.success(request, |         messages.success( | ||||||
|  |             request, | ||||||
|             "Applied manual refund to credit note." |             "Applied manual refund to credit note." | ||||||
|         ) |         ) | ||||||
|         return redirect("invoice", invoice.id) |         return redirect("invoice", invoice.id) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer