Jump to content
  • 0

Complex discounting... Any addons/plugins, can this be done?


seebs

Question

Okay, so, in existing stuff that we wish to migrate to Blesta, we have line items, and every line item can have its own discount. And if the amount of a thing changes (something like storage usage or bandwidth), discounts apply to that change.

In Blesta, the only discounting option I'm seeing is coupons. Coupons apply per-package, so I can't have one discount rate for one line item, and another discount rate for another.

But possibly more concerningly, coupons are applied instantaneously at the time of invoice creation, and don't adjust to reflect future changes to the invoice. There's some data gathering that's gonna take time to get properly integrated/automated, meaning in some cases we'll want to edit an invoice to change the quantity of an item. But if the item's discounted, this... doesn't work.

So what I'm looking for is an alternative path. I think I could make a plugin which adds a table that can record discount rates associated with services, or individual line items... But I don't see any obvious way to then override an invoice's calculations about how much it thinks is owed. I suppose I could do it the other way around, and have the plugin do the computation and set price overrides, maybe?

Basically, I don't understand the architecture well enough to be sure even what I should be asking. My ideal goal would be, when creating a service subscription, to be able to enter a discount percentage for each line item separately (say, addons within a package, one customer could get 20% off package option A and 25% off option B, another could get 30% off A and 0% off B, so discounts are unique to a specific customer/option pair), and any edits to the invoice that affect quantities would still correctly reflect the discount.

I'm pretty sure that the coupon system is entirely irrelevant to the intended use case here, honestly; it's at the wrong granularity and it's a one-time computation. But I don't immediately see any hooks in the plugin architecture to let me override an invoice's computation of totals.

Link to comment
Share on other sites

11 answers to this question

Recommended Posts

  • 0

You may want to look at https://docs.blesta.com/display/dev/Event+Handlers

Using an Event/Hook will allow you to tie into something like Invoices.add or Invoices.edit to trigger your plugin to immediately modify the invoice to reflect whatever discounts you want to reflect.

Blesta also supports price overrides, which can be set for each service individually. Overrides override the normal package price, but not any configurable options.

Link to comment
Share on other sites

  • 0

I was looking at the event hooks, but it looks like I still can't actually do what I want with those, because the actual computation of "the cost of this line item" seems pretty baked in. And I want to be able to do things on a per-option basis.

What I'm looking at specifically is:

    public function getSubtotal($invoice_id)
    {   
        $subtotal = 0;
        $sub = $this->Record->select(['SUM(IFNULL(invoice_lines.amount*invoice_lines.qty,0))' => 'subtotal'], false)->
            from('invoice_lines')->where('invoice_lines.invoice_id', '=', $invoice_id)->fetch();

		[...]
	}

My basic thought is to add a discount rate field to the invoice lines table, and modify this computation to use it, and then also add UI widgets to various places. But I don't think this is possible with plugins or hooks; there's no way to intercept this computation and alter it.

... Which means I'd be looking at making an actual change to core to add this feature, whereupon the question is, are you at all interested in contributed feature code? Because I'd rather not maintain a fork, but this looks like something which is almost trivial to do if it's being done in the core system, and extremely difficult to get right in a plugin. The basic goal is to just allow every line item in an invoice to have a discount rate which is computed individually-per-line-item, and can be displayed in invoices.

To clarify: Part of the goal here is not just to change the values, but to have something indicating what is going on. Without a discount field, there's no easy way to know how much to change the value, and there's no way to reliably display the correct result; I could sort of fudge it by computing the expected price, comparing to the overridden price, and displaying the difference, but that'd have rounding issues, etc. So I'd rather just have the data actually model what's happening, which is something like "this customer gets 25% off on this specific package addon".

Edited by seebs
clarifying
Link to comment
Share on other sites

  • 0

What I was thinking was that you'd be alerted by the hook and then make a modification to the line items as necessary. I think I don't understand enough about what you're trying to do, and it's the Friday before a long weekend so my mind is a bit distracted.

If there's another hook we could create to accomplish your goal, we'd be interested in looking at that. However, if modifying the core is the only way to do it, we might be interested in possibly adding it to the core. It'll depend, we'll have to look at what you've come up with and make a decision internally based on the direction of Blesta.

Link to comment
Share on other sites

  • 0

Yeah, I hear you on the Friday thing. This was a heck of a week for me, so I'm a bit fuzzy.

The issue I have right now is that, without a distinct "discount" field per line item, there's nothing I can store in the existing fields that correctly indicates the state I want to indicate, which is basically "you have ten X, they're $9 each, you get a 25% discount, so that's $90-(25%) => $67.50. But we want the display to indicate the discount specifically.

And we can't really achieve this with coupons. With coupons, if we make an invoice for you, and you have 10 X, and a coupon that provides a 25% discount, the discount amount is computed based on that. If we then edit the invoice to indicate that you have 12 X, the discount line item still reflects 25% of the pricing of 10 X. (This matters, in our use case, because we anticipate at least a while during which we'd be creating invoices automatically, but updating some quantities after the fact with information we haven't got properly integrated yet.)

And I think that's partially just because coupons are applied at invoice generation, and then the generated invoice doesn't really have a coupon anymore, just a fixed dollar amount discount.

Thinking about it, I note there's currently no model for "line items" in and of themselves. So services and invoices interact with line items, but for instance, the subtotal computation in the invoice is requesting a sum from SQL, not requesting the individual line item values and then summing them. I don't think this is enough complexity to want to make line items their own model.

Anyway, I think the points that would need to be changed would be (1) line items need a discount, (2) invoice edit/view/etc need to be able to display the discount field, (3) services need to be able to record discounts on things so they can create line items with matching discounts when the services apply. And I think all of these are reasonably simple.

Link to comment
Share on other sites

  • 0

1 min pleaese.

why not leave the first line of item as it without any change, then add a line discount for it with negative amount. in the past we have made a plugin for a client that do maybe the same request but with a whole discount with free domain, so the Packages X, XX, XXX who include the domain Y, YY, we include a 100% coupj discount for the domain, wo they can offer free domain with only some packages, in creation and renewals, and is working perfectly .

in the plugin setting we have page that he select the terms that will have free domains, and another setting for selecting the free applicable domains packages.

 so finnaly you can have invoice with lines

Line Description                                                                     , Qnt , Unit Price  , Total

package name (from ...to ....) 12 mounth                                , 1     ,     10$      , 10$

10% Discount for package name (from ...to ....) 12 mounth    , 1     ,     -1$       ,  -1$

Total Invoice                                                                                                       ;   9$

is this what you want  ?

 

Link to comment
Share on other sites

  • 0

I don't think it is. I can fake up the presentation however I want by overwriting the invoice template, but it seems to me that if the original line item were a package option with a number, making a separate line item for the discount means that I now need some way to track that the discount is related to the previous line item, so I know to update it if the package option gets altered. So, if someone were to edit that invoice and change the quantity of the first line to 2, the discount wouldn't necessarily track it.

So I'd rather just have an actual per-line discount rate, expressed as a percentage probably.

Link to comment
Share on other sites

  • 0

As a followup: I do not at all understand the interaction between the rest of the line item computations, and the magic in core/Pricing/Presenter/Collection/CollectionItems.php and friends. They have references to "discounts" but I can't figure out what those relate to at all. They don't appear to be coupons, exactly, but I'm not sure what they are. Like, they don't appear to participate in invoice->GetSubtotal() or anything.

It *looks* like the places I need to add a discount are invoice_lines, invoice_recur_lines, and service_options. With service_options controlling the creation of plain invoices from services, and invoice_recur_lines controlling the creation of plain invoices from recurring invoices. But both of those paths lead to an invoice which just has static invoice_lines.

Also I can't find anything that calls invoice->makeLinesFromItems, which means I'm probably missing something.

Link to comment
Share on other sites

  • 0

Okay, so, say we have virtual hosting, and because it's 1995 again, we're charging $1/month per megabyte of storage. So, a customer has a monthly service package, which includes a service addon of "storage", billed at $1/unit. So if they have 500 units of storage, they get charged $500. And for some reason, we can't actually have the service value be exact, we have to do data gathering and update things. So the invoice gets created with a placeholder service that shows quantity 1, but then someone goes, checks the space the customer's using, and edits the value on the invoice.

But $1/month is too high, so people negotiate us down. So someone's getting a 50% discount on storage.

So, we automatically generate a placeholder invoice, which shows them getting 1 unit of storage for $1, and then we change the usage manually to 250, which would be $250.

But they get a 50% discount. So it shows them getting 1 unit of storage for $0.50, and then we change the usage manually to 250, and now it says $125.

If the discount is a separate line item with a negative dollar amount, what should that dollar amount be? It should be $0.50 when storage is 1, and $125 when storage is 250. And it won't change automatically, which is annoying. Also, displaying it on the invoice is harder.

But with my patch (now partially implemented, but I haven't got the UI fixed for Services yet), it's just automatic. Whatever you set the quantity to, the price is reduced by 50%.

One complexity is, there's a few places which do sum(x.amount*x.qty) in SQL queries, and we can't do that. But I think that's wrong anyway, because that moves the rounding to the wrong place. Each line should be fully rounded independently. (Say you have a pro-rated invoice with two $0.25 things on it, and exactly half a term for each. Our accounting people require us to compute each line as $0.12 and total to $0.24.)

So right now, the subtotal display for recurring invoices is wrong, because it's grabbing a single subtotal for the whole invoice rather than separately selecting the invoice_recur_line entries and computing their values, and I haven't figured out where to put the UI elements for editing discounts in Services, but the invoices themselves are working.

Link to comment
Share on other sites

  • 0

Okay, so, it's pretty easy to make this work for invoices themselves, and also pretty easy for recurring invoices.

For services... It's a lot harder.

The issue here is that a whole lot of stuff assumes that package configuration will have exactly one datum per possible option, so of course that is the value-or-quantity. And this means that the services code, and the package_options code, and the admin and client controllers, all rely on the assumption that you can have a single table of [option_id -> value] items that is everything you need to know about a package's configuration. So, what if I wanted a second item of data, which is what discount rate if any to apply to this option? Welp.

And this is additionally complicated because I genuinely can't figure out the meaning of the "value" field in package_option_values. It gets used in some cases as a quantity, but only in some, but it looks as though a value set there would override values set explicitly elsewhere, maybe? So... I'm not sure what that's for. There's only one package_option_values entry for a given thing, even if there's several services configured which have that option and specify different amounts; that's the qty field of service_options. So... I don't get what "value" is for. (Maybe it makes more sense for non-quantities?)

So it seems to me that probably the right choice is not to try to overload the config_options/configoptions channel, but I'm still a bit unclear on the relationships between the package_options model and the service_options stuff. It looks like editing a service is using package_options to decide which things to display, but is somehow picking up existing service_options values to set initial values for those fields.

Link to comment
Share on other sites

  • 0

and a followup: got that working, by adding a parallel configdiscounts/config_discounts thing. With that in place, I've gotten a service to autorenew with appropriate discounts. I don't know if prorating is working correctly, I haven't actually tested recurring invoices, and I need to get the default invoice template to show the thing, but the functionality is now working.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...