dinoperovic / django-salesman

Headless e-commerce framework for Django and Wagtail.
https://django-salesman.rtfd.io
BSD 3-Clause "New" or "Revised" License
384 stars 46 forks source link

Stock Handling #18

Open IncreaseComputers opened 2 years ago

IncreaseComputers commented 2 years ago

I'm looking to implement stock handling for order s/ basket items - handling the stock is fine the problem I'm having is catching basket quantity changes to adjust stock levels, any ideas or suggestions on doing this?

IncreaseComputers commented 2 years ago

I think the item validator would do the job if it could be raised on item deletes/ basket clears

dinoperovic commented 2 years ago

@IncreaseComputers could you perhaps use Django's post_save and post_delete signals on the BaksetItem model to sync with your stock management?

The basket item validator is used just for validating the item, it is not aware of deletes.

IncreaseComputers commented 2 years ago

Signals wont work as getting the original values from the objects is problematic - Ie in basket save has the quantity changed? what was the old quantity what is the new quantity? - I have implemented a "stock _handler" override similar to the validators and hooked into it at various points in basket and basket item to deal with changes - it passes a operation "delete" / "add" /"update" the original object and the changed fields - it works but it means overriding core code making it more difficult to maintain

dinoperovic commented 2 years ago

I see what you're saying. I suppose you could swap the basket models and add the additional field that keeps track of the "previous quantity" on the basket item. You could do this using the swappable models feature: https://django-salesman.readthedocs.io/en/latest/advanced/swappable_models.html

The thing with stock handling is that it can differ from the shop implementation making it hard to put specific logic into the core project. Maybe having dedicated signals for whenever an item is added/updated/deleted on the basket would solve this.

I would also consider a different approach to stock management, one where it does not tie to the basket directly. I had previously used a system like this:

This way you're not updating the stock on every basket item add/remove, but rather when the intent to buy the items is created. Also, users tend to add items to the basket and leave the shop, with your approach you would have decreased the stock for that item indefinitely -- this can, of course, be handled by implementing an abandoned cart feature, but you see my point.

IncreaseComputers commented 2 years ago

one of the problems (which is specific to my implementation) is that I'm selling memberships - which have a limit - so I have to follow the basket rather than the order - I cant oversell... - I will stick with my overrides for now - I'm planning to also implement the methods into the order to also convert a "reservation" to a completed stock adjustment.

IncreaseComputers commented 2 years ago

To handle the abandoned item I have added an "expiry" timer to the item which removes it from the cart after a specified time

dinoperovic commented 2 years ago

@IncreaseComputers I believe you would still avoid overselling in my example by raising ValidationError in validate_basket method on the payment if any one of the items is not available.

Either way, here's an example implementation using Django signals without overriding the basket item model:

from django.dispatch import receiver
from django.db.models.signals import post_delete, post_init, post_save

from salesman.core.utils import get_salesman_model

BasketItem = get_salesman_model("BasketItem")

@receiver(post_init, sender=BasketItem)
def post_init_item(sender, instance, **kwargs):
    # Remember current quantity on the instance
    instance._current_quantity = 0 if instance.pk is None else instance.quantity

@receiver(post_save, sender=BasketItem)
def post_save_item(sender, instance, **kwargs):
    quantity_diff = instance.quantity - instance._current_quantity
    if quantity_diff > 0:
        print(f"Decrease {instance.product} stock by {quantity_diff}")
    elif quantity_diff < 0:
        print(f"Increase {instance.product} stock by {quantity_diff * -1}")

@receiver(post_delete, sender=BasketItem)
def post_delete_item(sender, instance, **kwargs):
    print(f"Increase {instance.product} stock for {instance.quantity}")

You can of course override the __init__, save and delete methods on basket item directly using swappable models.

I am considering adding the item_quantity_changed signal that would basically do this by default, but I'm still not sure if it would be helpful for any other cases.

pablondrina commented 2 years ago

By the way, I would appreciate this item_quantity_changed signal ok

Thanks for this amazing job!

Em sex., 29 de abr. de 2022 às 09:06, Dino Perovic @.***> escreveu:

@IncreaseComputers https://github.com/IncreaseComputers I believe you would still avoid overselling in my example by raising ValidationError in validate_basket method on the payment if any one of the items is not available.

Either way, here's an example implementation using Django signals without overriding the basket item model:

from django.db.models.signals import post_delete, post_init, post_save from salesman.core.utils import get_salesman_model BasketItem = get_salesman_model("BasketItem")

@receiver(post_init, sender=BasketItem)def post_init_item(sender, instance, **kwargs):

Remember current quantity on the instance

instance._current_quantity = 0 if instance.pk is None else instance.quantity

@receiver(post_save, sender=BasketItem)def post_save_item(sender, instance, *kwargs): quantity_diff = instance.quantity - instance._current_quantity if quantity_diff > 0: print(f"Decrease {instance.product} stock by {quantity_diff}") elif quantity_diff < 0: print(f"Increase {instance.product} stock by {quantity_diff -1}")

@receiver(post_delete, sender=BasketItem)def post_delete_item(sender, instance, **kwargs): print(f"Increase {instance.product} stock for {instance.quantity}")

You can of course override the init, save and delete methods on basket item directly using swappable models.

I am considering adding the item_quantity_changed signal that would basically do this by default, but I'm still not sure if it would be helpful for any other cases.

— Reply to this email directly, view it on GitHub https://github.com/dinoperovic/django-salesman/issues/18#issuecomment-1113236648, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFQYBTZ3MR4Y55SCWLQVJ5DVHPGFPANCNFSM5UIWS6KQ . You are receiving this because you are subscribed to this thread.Message ID: @.***>

-- ¶ Pablo Valentini Nelson Boulangerie @.***>(43) 3321 3954 comercial (43) 8404 9009 celular