Commit Graph
42 Commits
Author SHA1 Message Date
Romain Derie e8efcb1bc3 [FIX] sale_coupon: do not deduce reward quantity on current_order
Before this commit:
1. We were checking if the rewards were in the SO even if it was 'next_order'
   program.
2. We were deducing the rewards quantity for 'next_order' program when checking
   if we had enough products to meet the program requirements.

Now:
1. We don't verify if the reward is on the SO if the program is 'next_order'
2. We don't deduce reward quantity when checking if enough products to meet the
   program requirements if the program is 'next_order'

opw-1907211
Closes #3062
2018-11-15 15:56:25 +01:00
Romain Derie 7b99c0db92 [FIX] sale_coupon: generate only one coupon per program per SO
Before this commit:
Everytime the SO met a 'on_next_order' program requirements, a coupon would be
generated on that SO. Even if there was already a coupon for that program set to
'expired', for instance when the SO did not met the requirements anymore.

Now:
When the requirements are met, it checks that an `expired` coupon does not
already exist.
It it does, it will set that coupon back to `reserved` state.

Step to reproduce:
  - Create a program on `next_order` with requirements, eg $300 amount
  - Create a SO and add $300 worth of products
  - Update promotion, a coupon is created and set to `reserved`
  - Now update the SO to go below $300
  - Update promotion, the coupon is set to `expired` as it should
  - Update again the SO to meet the $300 requirements again
  - Update promotion, a new `reserved` coupon is generated instead of updating
    the state of the previous one from `expired` to `reserved`

Closes #3062
2018-11-15 15:56:19 +01:00
Adrian Torres 51c7afff48 [FIX] sale_coupon: restore SO processing time
This optimizes the confirmation of a sale_order in the ecommerce by ~73%,
for a cart with 160 unique items, the processing time goes from
7m46s to 39s, which is roughly the same amount of processing time if the
module `sale_coupon` is not installed.

This optimization is achieved in two steps:

1. By validating promotional/discounted products in batch instead of
validating each product one at a time.

2. By performing an unlink() on invalid reward lines instead of sending
a delete command by writing to the one2many, as delete commands are not
(yet) performed in batch, which means record recomputation after every
delete.

opw-1893277
2018-11-15 15:56:14 +01:00
Goffin Simon 7633f96306 [FIX] sale_coupon: promotion program with fixed amount
Steps to reproduce the bug:

- Create a promotion program P with a fixed amount discount
- Set a customer tax T on the discount product generated for P
- Create a SO with one line
- Apply the promotion program by clicking on "Update promotions"

Bug:

The SO line with the discount product is generated but the tax T was missing.

opw:1888484
2018-10-17 15:24:33 +02:00
Romain Derie af0e47f42f [FIX] sale_coupon: compute fixed amount as such
Commit 092200073 broke the discount computation for discount of type
fixed_amount. Indeed the appropriate method was not called anymore and such
discount type would be computed as percentage discount with default value of
10% (field default for percentage).

opw-1888484
2018-09-28 18:14:10 +02:00
Romain Derie 85c89c300a [FIX] sale_coupon: dont show strikethrough if pricelist show discount
Using a pricelist set as `without_discount` will show a strikethrough price on
reward line (discount and free product).
This is happening because the program reward line (discount_line_product_id) is
created with the default 1$ price as this price is not usefull when computing
coupons. Only the line price is checked.

Now, we create a reward line with a -1 000 000$ price to prevent this UI
behavior.

Step to reproduce:
  - Activate pricelist (Sales>Config>Settings>Pricelist formulas)
  - Activate discounts in Sales Configuration (Manual discount on order lines)
  - Set pricelist to 'Show public price & discount to the customer'
  - Add a discount in your cart
  - There in a strikethrough price of $1 above the discount line price

task-1878245
2018-09-13 15:21:51 +02:00
Romain Derie 452e906396 [FIX] (website_)sale_coupon: _compute_tax_id with tax on reward line
It should get tax from the SOL and not the SO's product as the tax is applied
on the SOL only (the reward product has no tax by default).
2018-09-13 15:20:40 +02:00
Romain Derie 5ff0b842df [FIX] sale_coupon: delete 'sibling' lines when deleting a reward line
A discount on products with different taxes will result in as many discount
lines as there is different taxes.

Before this commit, removing one of these lines would not do anything else.
Then, recomputing the coupon would regenerate/add back this line.
On e-commerce, it means you would never be able to remove the program by
deleting its reward lines if there are multiple taxes (and so multiple lines)
since it would `recompute_coupon_lines` right away, adding it back directly.

Now, this commit will ensure that removing one of these lines will also remove
the other ones coming from this program on other taxes.
It means removing a regular product will remove it's reward line that will also
now remove the other program's discount line on other taxes and ultimately will
remove the program from the SO since all the program reward lines got removed.
(It won't affect auto applied program since it will be regenerated afterward)

task-1866977
Related to #2224
2018-09-13 15:16:57 +02:00
Romain Derie 034fb31c40 [FIX] sale_coupon(_delivery): fix some flows (mostly on next order)
This commit is the continuity of a42c27f9 that fixed some bugs.
One of the bug was that a discount was only applied on price without taxes.
E.G: A $100 product with 20% excluded tax would cost $120 but a 50% discount
     would give the user only $50 discount and not $60 as expected.
     User would lose $10 discount.
With these fixes, the discount line would correctly be set to $60 but since the
discount line would not have tax, the merchant would lose the discount amount
on the tax, in this case 50% of $20.

This commit will improve this case by adding a tax on a discount line.
Thus, since a global discount can be applied to multiple products having
different taxes or even multiple taxes on the same product, a discount line
should be split for every product taxes the discount applies on.
Obviously, this is a big behavior change, even if the discount is now finally
correct: the discount is applied on the total price and the merchant does not
lose any taxes.

To make it more clear, the discount lines description will mention on which
taxes it is applied.

There won't be any changes in the e-commerce since we will group the discount
lines so it will appear as before even if there are multiple lines. This is
only possible because we don't show taxes on line on the e-commerce.

This commit also fixes more complex flows/use cases.
Note: Fixes & flows will be illustrated with a SO but the behavior is the
      same with a cart on front-end.

== Fixes ==
===========================================================================
The biggest flow that needed to be fixed is when a promotion program give a
reward on the next order.
Let's illustrate with a promotion program giving a free `product_B` on next
order (so it will generate a coupon) if the SO meets the requirements.
The requirements are: buy a `product_A` and buy for at least $600.
(This program needs a code: 'free_b_on_next')

1. If the SO has a `product_A` and over $600, applying the promotion code
   `free_b_on_next` will error saying that `product_B` is not in the cart.
   This error should not appear, since the `product_B` is the reward for
   the next sale order that will use the generated coupon.

Anyway, if you add `product_B` and apply the promotion code `free_b_on_next`
to generate the coupon to check the rest of the flow. It will generate the
coupon and send email to the customer.

2. Every time you will apply the promotion code `free_b_on_next`, it will
   regenerate a new coupon and resend a new email to the client.

Then, create a second SO, add 2 `product_B` and try to apply the generated
coupon from the first SO.

3. It will error saying that the $600 requirement is not met.
   It should not since the requirement was only required to generate the
   coupon on the first SO. User has now a coupon to enjoy a free `product_B`
   and should not buy $600.

Anyway, let's add some products to meet the 600$ requirement to check the rest
of the flow.

4. It will now error saying that `product_A` is not in the cart.
   As bug 3., it should not since this requirement was also required to
   generate the coupon on the first SO.

Anyway, let's add `product_A` to meet the requirement to check the rest of the
flow.

5. Updating the promotions will remove the reward and you won't be able to
   reapply the coupon since it was `used`.
   This bug was even more critical in front-end since any action on the cart
   will recompute the coupons and automatically delete the reward after adding
   it. The customer never saw the reward on his cart.
   This bug is related to 3. and 4. Indeed, when recomputing coupons, it will
   remove programs that are not applicable anymore. The code wrongly think it is
   the case with this coupon since we don't have $600 or a `product_A`.
   `_remove_invalid_reward_lines` should treat coupon coming from a promotion
   program set on `next_order` differently and not filter on requirements.
===========================================================================

6. (extension of 2.) Every time an user would meet the requirements of a
   promotion program generating a coupon for a next order, it would generate a
   coupon and send an email.
   E.g: If buy 2 iPads get 1 for free on next order. If user add and remove iPad
        for some reason, every time he has 2 iPads in his cart it will receive
        an email with a new coupon.

6'. When applying a promotion code that will reward next order, it will not be
    set in the SO `code_promo_program_id` (I did not change that behavior since
    it could make sense to be able to apply another coupon that will give -20%
    on current order).
    This will allow applying multiple promotion rewarding a next order and/or a
    promotion rewarding current order. (Which would not be possible if was
    stored on `code_promo_program_id` since you can only apply one code on an
    order)
    But this cause a bug: since the promotion program is not stored on the order,
    every time you will apply the coupon it will generate a new coupon and send
    an email.
    So if you work on a draft sale order and do some tests, it will send many
    emails to customer..

7. Having multiple global discounts (on the whole SO) was forbidden: you can't
   apply a coupon promotion or a program promotion if there is already a global
   coupon discount or a global promotion discount with code. But still you could
   apply a global discount if there were a global promotion discount automatically
   applied (without a code).

8. On the generate coupon wizard, if you select `Number of Selected Customers`,
   by default it is set to 'Match all records (X records)'.
   But when you click on generate, it won't generate anything even if visually
   it selected every partner by default.
   This is because the domain default value is an empty string. Thus, the domain
   widget is selecting everything but once it is sent back to the server, since
   it's an empty string it won't get through the `if` condition.
   It was even weirder when you add something to the domain and remove it, you
   then visually come back to where you were at the beginning but the domain got
   changed from '' to '[]' so in this case it will generate the coupon for every
   partner.

== More like improvements ==

1. `action_cancel` and `action_confirm` would handle correctly the states of the
   applied and generated coupons (valid, consumed, expired..).
   But `action_draft` would not handle the states at all.

2. The generated coupon on a SO are now sent by email only when the SO is validated.
   This is part of the fix n°2 on coupon being generated multiple times and sent by
   email multiple times.
   The coupon sent by email was not usable anyway since it would error saying the
   SO that generated the coupon was not validated.

3. Discount line limited to a max amount (set in the program) will mention on
   the line description that the discount is limited.
   This will avoid cases where you don't understand why 50% discount of $2000
   SO shows $40.

4. If you archive a program and there is still some SO containing this program's
   reward line, these line will be 'floating free' and will never be updated or
   removed. Indeed, the `_remove_invalid_reward_lines` is basically checking
   which programs could be applied on the order and which one are applied on the
   order. If a program is applied on the order but is not supposed to, his
   reward line will be removed.
   Of course, since the program got archived, it won't get retrieve and the line
   will never get removed. Even if the case of a free iPad: if you buy an iPad
   and you removed the paid iPad from the SO. The free iPad will remain forever.
   Note: We can't remove these discount line directly when archiving a program
         because there might be quotations that we don't want to modify since it
         is waiting for customer confirmation.

Improved behavior:
  - Archiving a program archives its related reward product but unarchiving
    the program does not unarchived the product.

task-1866977

opw-1832967
opw-1857843

Closes #2224
Comes with https://github.com/odoo/odoo/pull/24971
2018-09-13 15:16:57 +02:00
Romain Derie 0a565abfad [FIX] sale_coupon(_delivery): correct discount when specific product
Before this commit:
1. With a coupon set on 'specific_product', the discount amount was multiplied
   by the product quantity twice.
   (Introduced by a42c27f which apply discount on the full price by using
   price_total (already multiplied by quantity) over price_unit.
2. With a coupon set on 'cheapest_product', the discount was not discounting taxes.
3. With a coupon set on 'cheapest_product' and `sale_coupon_delivery` installed, it
   would simply crashes and throws an error since `sale.order.line` has no
   `program_id` attribute.

Now:
1. We no longer multiply by the quantity twice;
2. We apply discount on the full price (with taxes);
3. We use correct attribute to avoid crash.

opw-1830986
2018-03-29 13:14:06 +02:00
Romain Derie 61d049ab4c [FIX] sale_coupon: recompute coupon before confirmation
Instead of after
If "Lock Confirmed Orders" option is checked, a sale order with a coupon can not
be confirmed as the lines are recomputed after the change of state

Backport of https://github.com/odoo/enterprise/commit/073ca5866a3a7006a03b99c4681e60da7a6e6efd

Closes #1967
2018-03-16 11:14:12 +01:00
Christophe Matthieu ce054681f0 [FIX] sale_coupon: change unit
When the reward product is changed, the corresponding UOM should be
adapted.

opw-1823149
2018-03-13 15:47:56 +01:00
Christophe Matthieu 75f39e07b2 [FIX] sale_coupon: can duplicate sale order lines
Fix the commit a42c27f99e0061b7c4f6597e4 which inverts the initial
filter. The change remove all sale order lines for the simple order
create in the backend.

opw-1819977
2018-03-13 12:53:31 +01:00
Romain Derie ef5d056403 [FIX] sale_coupon: fix some use cases
Some use cases were wrong, this commit attempt to fix those.
Here is the most important ones:

1. Discount was offered on free product 'value' aswell

Cart: 4 iPad Mini + one free offered + 10% discount

                      |    qty | unit value |   line total |
	----------------------------------------------------
	iPad Mini     |      4 |        320 |         1280 |
	- 10% discount|      1 |        128 |          128 |
	= 1152
	- free product|      1 |        320 |          320 |
	= 832

Should be

	              |    qty | unit value |   line total |
	----------------------------------------------------
	iPad Mini     |      4 |        320 |         1280 |
	- free product|      1 |        320 |          320 |
	= 960
	- 10% discount|      1 |         96 |           96 |
	= 864

The error result is $32 which is the 10% discount applied on the free iPad.

2. Decreasing product quantity below the minimum threshold to be elligible for
the promotion doesn't remove the product

  - (With a promotion offering a free iPad when you buy 3 (fourth is free))
  - Create a quotation with 3 iPads Mini, save and update promotions, you don't
    have a free one, which is 'correct'
  - Edit, change the quantity to 4 iPads, save, update promotions, you have a
    free iPad (reduced from total), still correct
  - Edit, change the quantity back to 3 ipad, update ptomotions, you still have
    a free iPad even if you only bought 3 of them (the way it is designed, the
    free iPad should only appears when you order your fourth).

The error result in a 'loss' of $320 since at the end we are offering a free
iPad and the user only pays 2 instead of 3.

3. Free product will be considered as regular product when updating quantity
of regular product afterward

This error is kind of exponential: the more you play with the flow explained
bellow, the more incorrect it becomes.

Buy 100 iPads, get 25 free iPads.
Then set quantity (on regular iPad) to 50, it will recalculate the amount of
free iPad thinking the 25 previous free iPads are regular iPad.
(50 + 25) = 75 regular iPads = 18 free iPads (25 previous free iPads were
wrongly used for the calculation but they are simply gone at this point).
Cart is now 50 regular iPads and 18 free one, total: $10240 (50×320 - 18×320).
It should be 12 free iPads, not 18, so $12160 (50×320 − 12×320)

The error result in a loss of $1920: ~ 20% of the total amount of the order.

This error will also appear the other way around if you add quantity instead of
removing some.
Playing with this error multiple times, you can arrive to a negative total
(more free than paid products), 0 total or very low total ($320 for 100 iPads).

4. Free product are wrongly calculated, user get too many of them

  - Create a quotation with 10 iPads, you get 3 free iPads instead of 2
  - Indeed, your first 4 iPads should give you a free one
  - Your next 4 iPads should give you a second one (you got 8)
  - The last 2 (to reach 10) are not enough to give you a third free iPad

The error result in a loss of $320 out of a total of 2560$ which is ~ 15%

5. Paid/regular products related to a promotion won't be taken into account
 when checking if you have the minimum amount threshold to apply the promotion

Promotion: 3 Computer Case(25$) = 1 Free Little Server (40.000$), auto applied
  -Have a cart with 3 'Computer Case' and 2 'Little Server'
  -One little server of the 2 is free (because you have 3 computer cases)
  -You so pay 3x25 + 1x40000 which is 40075$
  -Create a promo code that will give you 10% discount if you bought over $2000
  -You won't get the reduction even if you bought over $40.000 because the
   little server you pay is considered as the product of the reward product
   even if you dont have it free. It doesn't check how many regular product
   should be substracted according to the number of free product you have

6. Wrong product is checked for the required quantity to apply the promotion

Promotion: 3 iPads Mini ($320) = 1 free Bose Mini Bluetooth Speaker ($247)

  - Add 8 iPads Mini and a Bose Mini Bluetooth Speaker to your cart
  - The system should give you a free Bose Mini Bluetooth Speaker since you
    only have one regular Bose
  - But the system is giving you 2 free Bose Mini Bluetooth Speaker

Cart is: 8 iPad ($2560), 1 Bose ($247), 2 free Bose (-$496)
The system think that it should give you 2 free Bose because you are eligible
to, since you have 8 iPad, but it doesn't check that you have enough regular
Bose.

The error result in a loss of $247 out of a total of $2311

7. Auto applying promotion are not applied if there is already a promotion code
Promotion:
  - Free iPad Mini if you buy 3 (so the fourth become free), auto applied
  - 10% discount with code '10pc'

  - Add 4 iPads Mini, you got a free one
  - Set quantity back to 1 iPad, free one is gone
  - Add code 10pc, you got a 10% reduction line
  - Set quantity to 4 iPads again, you dont have the free iPad since there is
    already a promo code

This could make sense but it is not coherent since the 2 promotions would be
applied if they were both on auto apply mode.
Note:
  - If both promotions are on auto apply, you will get both
  - If 10% discount becomes auto applied and iPad promotion now has a code
    (reverse situation) it will work

8. Discount is only applied on untaxed amount, not on tax

Discount will ONLY be calculated on the untaxed amount and then reduced from
the sale order/cart total.
The taxes would be unchanged and charged like the product was full price.

Let's illustrate with a $100 product, a 15% tax and a 20% discount:

1. Tax included:
$100   : Product with 15% included taxes
$86.96 : Untaxed product
$13.04 : Taxes

After aplying 20% discount, result should be:
$80    : Product with 15% included taxes
$69.57 : Untaxed product
$10.43 : Taxes

Instead we have:
$82.61 : Product with 15% included taxes
$69.57 : Untaxed product
$13.04 : Taxes

There is a difference of $2.61 which is the result of the taxes not being
discounted: 13.04-10.43 = 2.61

2. Tax excluded:
$115 : Product with 15% excluded taxes
$100 : Untaxed product
$15  : Taxes

After aplying 20% discount, result should be:
$92  : Product with 15% included taxes
$80  : Untaxed product
$12  : Taxes

Instead we have:
$95  : Product with 15% included taxes
$80  : Untaxed product
$15  : Taxes

There is a difference of $3 which is the result of the taxes not being
discounted: 15-12 = 3

9. Taxes of the free shipping reward would be considered as paid value when
checking if required threshold is met.

------------------------------------------

Not really a bug, more an improvement:

1. The way it was designed, the rule_minimum_amount would check if your order
amount was over the required threshold to be eligible for the reward.
'Problem' is that it your order has already a discount (eg: 10%), it would
check if the price without discount is above the required amount.
I don't think it is coherent, let's illustrate:
  - First pomotion (auto applied): 1 iPad Mini for free if you buy over $2000
  - Second promotion (auto applied): 50% on order

If an user is buying for $2000, he is actually paying $1000 thanks to the 50%
discount.
Should we give him the free iPad?
The current implementation is giving it, which is not convenient IMHO.
Now, we use the amount the user really pay to check if he is elligible.

2. Since new promotions have the same sequence by default, they will be sorted
and applied by their creation order. This will result in inconsistent behavior:
  - Promotion 1 : 10% discount
  - Promotion 2 : free shipping
  - Both promotion are auto applied and need a minimum of $872.73 tax excluded

Let's illustrate with a $873 order:
If the promotions were created in the order above, you won't get both rewards
since the 10% discount will be applied first and will lower the SO total.
The order will then fall below the $872.73 required amount and won't be
eligible for the free shipping.
But if you created the free shipping promotion before the discount one, you
will be eligible for both since the free shipping will be applied first and
won't change the SO total.

To keep the same behavior independently of the promotion creation order, we now
sort by reward_type ('discount' > 'free_shipping' > 'product' to apply
discounts first.
This is better IMHO, since you want to give promotions with a minimum threshold
ONLY if the user really pays that amount. It wouldn't be the case if the total
is checked before the discount is applied.
2018-02-23 18:36:19 +01:00
Romain Derie 4c6b474505 [FIX] sale_coupon: min quantity set to 0 will crash backend and shop
Before this commit, if minimum product quantity was set to 0 on a coupon
program, it would then throw:
  - On backend, when updating promotions
  - On frontend (shop), user won't access his cart (error 500)

Now, we prevent quantity from being lower than 1

Step to reproduce:
  - Create a coupon automatically applied, set quantity to 0, set reward to
    'Free Product' and set free product to iPad Mini
  - Go to /shop and add a iPad Mini to your cart, it will throw an error 500
    instead of redirecting to your cart
  - Go to backend, click on 'Update Promotions' on a quotation, it will show an
    error popup
2018-01-12 12:37:48 +01:00
Jeremy Kersten 368f14bd0c [FIX] sale_coupon: fix performance issue during coupon creation
When you create a coupon, before this commit, all account move line was rewrite with the currency (useless OP).
Now the creation for a db of 15k lines tkaes 2 secondes instead of 10 minutes.
2017-12-12 18:15:11 +01:00
Romain Derie b236ca3f15 [FIX] sale_coupon: incorrect free product qty
Note: The following bugs were also impacting the e-commerce.
This commit fixes some bugs related to promotion program offering free product:
  - Create a promotion program that offers an ipad if you buy 3 of them, so the
    fourth is free (reduced in total)

1. Buying 4, getting a free one, removing one, free one remains
  - Create a quotation with 3 ipad, save and update promotions, you don't have
     a free one, which is 'correct'
  - Edit, change the qty to 4 ipad, save, update promotions, you have a free
    ipad (reduced from total), still correct
  - Edit, change the qty back to 3 ipad, update ptomotions, you still have a
    free ipad even if you only bought 3 of them (the way it is designed, the
    free ipad should only appears when you order your fourth).

2. Exponential bug: buy a lot of ipad, get a lot of free ipad then set the qty
to a lower amount, you will have a lot more free ipad than you should.
It can even lead to a negative amount (more free ipad than you paid ipad, but
in that case you won't be able to confirm the sale).
  - Create a quotation with 100 ipad, you get 31 free ipad (which is incorrect,
    see bug 3)
  - Edit and change the qty back to 10 ipad and save
  - Update promotions, you will have 10 ipad to pay and 10 free ipad resulting
    in a null total.

3. Buy X ipad, get Y+Z free instead of Y (lets illustrate with 10)
  - Create a quotation with 10 ipad, you get 3 free ipad instead of 2
  - Indeed, your first 4 ipad should give you a free one
  - Your next 4 ipad should give you a second one (you got 8)
  - The last 2 (to reach 10) are not enough to give you a third free ipad

This is because free products are reward lines in the SO (is_reward_line) but
not linked to an applied_coupon_ids

Now, these reward lines will be considered as such and the coupon computing
will return correct results

This closes #1643
2017-11-29 16:23:56 +01:00
Nicolas Martinelli bb5d999596 [FIX] sale_coupon: default invoicing policy
The default invoicing policy should always be by ordered quantity.
Otherwise, if the user changes the default invoicing policy to
"delivered quantity", coupon won't be applied to the invoice.

opw-749632
2017-06-30 14:17:44 +02:00
Christophe Simonis a5bf3016bb [FIX] sale_coupon: remove useless @depends
Theses fields are now deprecated.
2017-06-21 17:32:22 +02:00
Yannick Tivisse 8f6ec58757 [FIX] sale_coupon: Don't update automatic rewards on the fly
Purpose
=======

Currently we are updating the coupon rewards on the fly when creating/modifying/deleting a sales order line.

To avoid multiple searches on res_partners and product to know whether the customer/product is valid on the sale coupon program, we store the applicable customers and products on the program according to the domain defined on it.

This brings 2 issues:
- The customers/products stored on the program are not updated (Was the original spec)
- The checks are numerous and maybe overkill for the given results

Specification
=============

- Don't recompute the reward lines on SO modifications. Add a button to compute the line by hand and make an automatic compute on the SO validation or on the page shop/payment.

- When recomputing the reward lines, make a search on the res_partners and products instead. Now that the action is not triggered so often we can afford to make a search.

- Don't use the fields rule_partners_domain and rule_products_domain. This should be removed in master.

- The field is_public_included is useless now. And we decided to make the coupon available for all the partners if they fit the domain on the program.
2017-06-21 15:55:27 +02:00
Yannick Tivisse c32fb6e358 [FIX] sale_coupon: Display an explicit warning message if reward isn't encoded on SO 2017-06-21 15:54:15 +02:00
Yannick Tivisse 9eb48f8275 [FIX] sale_coupon: Display an explicit warning message if partner can't have the reward 2017-06-21 15:54:06 +02:00
Yannick Tivisse a8d084025d [FIX] sale_coupon: Allow coupon usage for non customers
Purpose
=======

On a sale coupon program, the allowed partners are the customers by default.

That way, users like the Administrator aren't allowed to use coupon on a fresh database

Specification
=============

Allow all the partners by default and the user restrict the partners as he wishes.

Before this commit:
Coupon Program: Only customers have access to rewards
Promotional Program: Only customers have access to reward and this can be retricted with a domain

After this commit:
Coupon Program: All partners have access to rewards
Promotional Program: All partners have access to reward and this can be retricted with a domain
2017-06-21 15:53:15 +02:00
Denis Ledoux be8d0cf716 [FIX] sale_coupon: unlink an empty set of order lines
This is possible that `unlink` is called without any record in self.
In the given case, this is because of
`self.env['sale.order.line'].search([('order_id', 'in', self.ids), ('is_delivery', '=', True)]).unlink()`
in the `undelivery_set` method of the module delivery.

`Unlink` on an empty set is supported, and it should therefore be the case here as well.
This is not because `self.order_id.no_code_promo_program_ids` expects a singleton
for the `self.order_id` part, and this is actually an empty set as `self` is an empty set.

Besides, it makes sense to loop on the lines passed to `self`,
as you could pass several lines, not all having the same
`product_id`, for the above line
`related_program = self.env['sale.coupon.program'].search([('discount_line_product_id', '=', self.product_id.id)])`

opw-746084
2017-06-07 17:25:12 +02:00
Yannick Tivisse 3c6b48c7b6 [FIX] sale_coupon_delivery: Do not unlink reward line if 'free_shipping'
The methods delivery_set and delivery_unset are called everywhere (mostly on the ecommerce). The reasons for that is that we do not want several shipping lines on the same order. A better way to achieve this would have been to create the line if it doesn't exist or write on it to modify the 'price_unit' instead.

The side effect of that behavior is:
- A delivery line is set on the order.
- We apply a coupon to get a free shipping
- We go on the payment page. The method _cart_update is called, which calls delivery_set, so delivery_unset. The shipping line is unlinked to create again a new shipping line with the price updated. As the shipping line is removed, a recomputation of the reward lines is made. The program isn't applicable anymore as there's no shipping line on the order. The reward is thus removed.

That way, it's impossible to have a free shipping reward on the ecommerce and achieve the full checkout process.

What's done in this commit:

If a free shipping is applicable by code or if a free shipping can be automatically applied. It will be done with a price of 0.0 if there's no shipping line or the price_unit on the shipping line if set. If the shipping line is removed (every 2 seconds on a checkout process), the reward line is not unlinked and the price is set to 0.0.

That way, it's now possible to make a full flow.

NB: If a line is unlinked on an order, we unlink the reward line if it's the last one set on the order. This is to avoid free shipping line coming back on a new order in the ecommerce even if not applicable.
2017-02-24 11:01:23 +01:00
Yannick Tivisse 7390b3246d [FIX] sale_coupon: Set taxes on reward line if free product 2017-02-24 11:01:23 +01:00
Yannick Tivisse 74ebc34fea [FIX] sale_coupon: Trigger correctly the reward recomputation methods
Use Case
========

In the coupon module, a call to a write, a create or an unlink on a sales order line may trigger a write, a create or an unlink on another line. Example: I have a promotional offer "If you buy for more than 100 euros, you got a 10 percent discount on the sales order. On my sales order, I buy 2 imac at 999 euros. At that moment my total amount is over 1000 euros and I got another line, the discount. I write on the sales order line that I only buy one imac, a unlink is trigger on the reward line to remove it from the sales order.

Current Solution
================

At the beginning, I was overwriting the write, the create and the unlink methods on the sales order line to perform the correct actions according to the promotional offers that were still applicable, not applicable anymore of newly applicable. This solution was followed by side effects on the computed fields. For example, a write on the SO line that was triggering an unlink on another line was triggering a computed field that depended on the sales order line. In that case, the environment I received on the compute method was the environment before the line unlink. As the compute method was looping on the sales order line, it was taking values on a line that wasn't existing anymore and that way it was triggering an 'This record doesn't exist or has been deleted' error.

After a discussion with @rco-odoo, we realized that the only way to achieve this was to override the write method on a sales order to avoid quadratic computation loop when creating/editing/deleting sale order lines. As coupon are recorded as sale.order.line, it may effectively blow up.

We also overrode the create method on a sales order line to make an ORM command on the sales order instead of a simple create on the sales order. See: https://github.com/odoo/enterprise/blob/saas-14/sale_coupon/models/sale_order.py#L253

For the write and the unlink, this is a little more complicated because a write may trigger a write and an unlink may trigger an unlink, resulting in an infinite loop.

New Solution
============

Having a method _recompute_coupon_lines on sale.order. This method do nothing if there is a key coupon_recompute in the context.

```python
@multi
def _recompute_coupon_lines(self):
   if self.env.context.get('coupon_recompute'):
       return
   self = self.with_context(coupon_recompute=True)   # avoid loop
   [...do actual stuff...]
```

The create/write/unlink method on sale.order.line call super before calling the method _recompute_coupon_lines

```python
@multi
def write(self, values):
    result = super(SaleOrderLine, self).write(values)
    self.mapped('order_id')._recompute_coupon_lines()
    return result
```

In order (pun intended) to accelerate mass creation/edition of lines, the method create/write/unlink of sale.order should call recompute themself

```python
@mutli
def write(self, values):
    result = super(SaleOrder, self.with_context(coupon_recompute=True)).write(values)
    self._recompute_coupon_lines()
    return result
```
2017-02-02 10:47:51 +01:00
Yannick Tivisse 2af2f4f558 [FIX] sale_coupon: Allow the public users to use the coupon mechanism 2017-01-31 10:31:37 +01:00
Yannick Tivisse 76e691f0fe [FIX] sale_coupon: Apply immediate programs on order line creation 2017-01-30 15:45:38 +01:00
Yannick Tivisse a00ebcedcc [FIX] sale_coupon: Assign a generated coupon to the order customer 2017-01-30 14:38:03 +01:00
Yannick Tivisse 6dd52a01a2 [FIX] sale_coupon: Do not validate generated coupons on SO cancellation 2017-01-30 14:37:32 +01:00
Yannick Tivisse 9a9b2ba89a [FIX] sale_coupon: Fix 'Expected singleton' traceback 2017-01-30 14:37:32 +01:00
Yannick Tivisse 7d277ff950 [FIX] sale_coupon: Remove promo code on automatically applied programs 2017-01-30 14:37:32 +01:00
Yannick Tivisse 98e7f5f710 [FIX] sale_coupon: Compute correclty the reward product qty if several lines
Use case
========

If apply base on a product, it takes only the first line of this product !
Eg: 1 free if 3 buy,
- add 2 line of 2 will not work,
- add 1 line of 1 (eg: discounted, descr, ...) and one ligne with 10... will be ignored
2017-01-30 14:37:31 +01:00
Yannick Tivisse 5947283e90 [FIX] sale_coupon: Set default discount value to 10.0 2017-01-30 14:37:31 +01:00
Yannick Tivisse 7ef998f103 [FIX] sale_coupon: Select only salable products on coupon programs 2017-01-30 14:37:31 +01:00
Yannick Tivisse 35332ac1e7 [FIX] sale_coupon: Select only customers on coupon programs 2017-01-30 14:37:31 +01:00
Yannick Tivisse d8a306aa71 [FIX] sale_coupon: Create immediate no code rewards on copy 2017-01-26 13:15:47 +01:00
Yannick Tivisse fc0c2368b5 [FIX] sale_coupon: Correct message generation in _check_coupon_code 2017-01-26 10:13:23 +01:00
Yannick Tivisse 034baaa82f [FIX] sale_coupon: Create a discount_line_product_id on program creation
When creating a program, we should always create a discount_line_product_id. Now on a product creation, if a product is not specified, the `vals` dictionary contains `discount_line_product_id: False` and the product is not created. It seems that the behavior on record creation has changed.
2017-01-17 09:46:49 +01:00
mdi-odoo 94066b25f5 [IMP] sale*: Improve the settings form view 2016-12-30 16:35:44 +01:00
pgu-odoo 5260d22c4d [ADD] sale_coupon: introduce coupon mechanism on sales order
Introduces 2 new concepts:

Promotion programs:
===================

Using promotion programs allows you to propose to your customers:
- Promotional offers limited in time or a limited order number.
- Public code printed in advertisements, magasines, etc.
- Specific offers based on a customers group or a product set.
- A combination of the preceeding points.

You may choose to apply the program reward immediately on the sales order or to propose the reward for a next order. In the second case a coupon will be generated at the order confirmation for the customer to use it later.

Coupon programs:
================

Using coupon programs allows you to propose to your customers:
- Free products or discounts if some conditions on products or purchased amount are met.
- Promotional offers limited in time.
- Generate coupons for a customers group.
- Specific offers based on a customers group or a product set.
- A combination of the preceeding points.

A coupon is applied by entering a valid code if the program rules are met and if the reward is already in the order lines.
2016-12-20 14:46:00 +01:00