Magento querying sales or order volume - magento

I am looking for a way to be able to get approx values of sales volume (basically how much money a store is generating) upon which I would like to base my rough calculations to show user some more data.
(Option A) I know I can directly query the database and then cache the results, but is Magento already doing that & caching it somewhere which I can just use? (Option B), like Magento saves the value of lifetime sales and average sales value somewhere.
Any data like daily/weekly/monthly average of sales or average order amount & daily/weekly/monthly average of orders would suffice here.
If the option A is only way to go here, how should I be querying this data? I have seen examples doing it like the following:
Mage::getResourceModel('sales/order_collection')
Mage::getModel('sales/order')->getCollection()
Which is faster & more lightweight for my needs? And any link where can I actually see detailed or well explained example of using them so that I can understand what parameters are available for querying in each case?
Update: I still don't understand clearly whats the difference between the above 2 methods, but my guess is one is more abstracted than the other & probably uses the other one internally but I am not sure. Anyways, I have this code snippet pulling in the data but I have a problem with filtering the data for a date range:
<?php
require_once 'app/Mage.php';
#umask(0);
Mage::app('default');
$orderTotals = Mage::getModel( 'sales/order' )->getCollection()
->addAttributeToFilter( 'status', Mage_Sales_Model_Order::STATE_COMPLETE )
->addAttributeToFilter( 'status', 'complete' )
//->addAttributeToFilter( 'created_at', array( 'from' => date( 'Y-m-d', strtotime( '-100 days' ) ) ) )
//->addAttributeToFilter( 'created_at', array( 'from' => date( 'Y-m-d', strtotime( '-100 days' ) ), 'to' => date( 'Y-m-d' ) ) )
->addAttributeToSelect( 'grand_total' )
->getColumnValues( 'grand_total' )
;
$totalSum = array_sum( $orderTotals );
$totalSum = Mage::helper( 'core' )->currency( $totalSum, true, false );
echo $totalSum . "\n";
Update:
This code snippet is working now. I had orders in the range but their order status was "processing", so I couldn't see them. I got the snippet from this question.
I am still looking for an explanation to what differs in the above 2 methods and which one is good to use?

In case someone comes here via Google like I did, this question motivated me to make my report page I've been wanting. I typically do not use the built in MVC (more below!) and instead will plop pages in var/export or something. I eventually came up with the below query after going through all the sales_flat_order* and sales_flat_invoice* tables finding a solution which represented the products we sold and shipped.
Sales report for this week:
SELECT sfi.grand_total as 'total',sfii.base_row_total as 'sales', sfii.sku as 'sku', sfii.qty as 'qty', sfo.increment_id as 'order'
FROM sales_flat_invoice_item sfii
LEFT JOIN sales_flat_invoice sfi ON sfii.parent_id = sfi.entity_id
LEFT JOIN sales_flat_order sfo ON sfi.order_id = sfo.entity_id
WHERE sfo.status = 'complete'
AND sfi.updated_at > "2013-06-01"
The date is this past Friday night at midnight. Note: if you add sfi.* to the SELECT you can do the math to double check shipping and taxes against the totals. The problem with sales_flat_order is that table doesn't represent cash in your checking account. We're not on accrual (and probably few small businesses are), so this is better for me understanding cash accounting basis.
For my report script, I also build a table of values and use Google Charts to make it pretty. The below page loads almost instantly:
Edited to add: While creating this above query, I discovered that a previous order querying tool built USING the proper means of collections, etc, misses a vast number of orders without any reason we could uncover. More and more, I'm finding the only way to get a job done correctly is to do it myself -- and do it the hard way.

Did you take a look # Admin -> Report -> Sales
/app/code/core/Mage/Adminhtml/controllers/Report/SalesController.php
/app/code/core/Mage/Adminhtml/Block/Report/Sales/Sales/Grid.php
Collection: 'sales/report_order_collection'
Tables:
sales_bestsellers_aggregated_*
sales_order_aggregated_*
report_viewed_product_*

Related

How to add filter on item collection to get the Sales rep(total Quantity and Total no of Orders) of those Sale Managers which are logged in

$collection = $this->_itemCollectionFactory->create()->getCollection();
$collection->getSelect()->columns(array('total_orders' => new \Zend_Db_Expr('COUNT(order_id)')))
->columns(array('total_qty' => new \Zend_Db_Expr('ROUND(SUM(qty_ordered))')))
->group('sku');
$collection->getSelect()->limit(3);
$collection->addFieldToFilter('store_id', 2);
Through this code i can get the total quantity and total number of orders of all sales rep. But i need only those total quantity and total orders which were done by the Sales Rep og Logged in Sale Manager.
As you mentioned in your question you only want to get the order info based on a certain sales rep. To do that you need to somehow find a unique identify for the currently logged in sales rep and add a filter for that .
For example:
$collection = $this->_itemCollectionFactory->create()->getCollection();
$collection->getSelect()->columns(array('total_orders' => new \Zend_Db_Expr('COUNT(order_id)')))
->columns(array('total_qty' => new \Zend_Db_Expr('ROUND(SUM(qty_ordered))')))
->group('sku');
$collection->getSelect()->limit(3);
$collection->addFieldToFilter('store_id', 2);
$collection->addFieldToFilter('sales_rep_identifier', $loggedInSalesRepIdentifier);
The label sales_rep_identifier and the value $loggedInSalesRepIdentifier are just examples, you may have to adjust the format in which the data is stored if it doesn't currently have a field to check natively what sales rep did it, and adjust the values there accordingly
If you're using some kind of prebuilt system then maybe there are other labels for the identifier you could use, without more information on the specific database structure it's impossible to answer exactly, but essentially you need to filter by the unique sales rep identifier, and if that doesn't exist now in the database it should somehow but added

Paginate sort on subquery column in CakePHP 3

This might be answered somewhere else already, but I was looking for an answer the past few days and couldn't find an answer that suited my problem/that I understood...
I'm using CakePHP 3.8.5 and am currently working on a query that includes a subquery in the select.
I got 3 tables Locations, Computers and Printers. Locations and Computers are in a belongsToMany relationship, as well as Locations and Printers.
So I'm trying to get the following query, which is working well as far as the data results go:
$computersQuery = $this->Computers->find();
$computersQuery->select([$computersQuery->func()->count('*')])
->where(function (QueryExpression $exp) {
return $exp
->notEq('Computers.Unwanted_Software', '')
->equalFields('Computers.Area_ID', 'Locations.Area_ID');
});
$printersQuery = $this->Printers->find();
$printersQuery->select($printersQuery->func()->count('*'))
->where(function (QueryExpression $exp) {
return $exp
->eq('Printers.WHQL', 0)
->equalFields('Printers.Area_ID', 'Locations.Area_ID');
});
$dataQuery = $this->Locations->find();
$dataQuery->select(['Locations.Area_ID',
'Unwanted_Software' => $computersQuery,
'Printers_Not_WHQL_Compatible' => $printersQuery])
->group('Locations.Area_ID');
So I'm trying to paginate the $dataQuery in my Controller. In my model I can click all three column headers but only the Area_ID column will get sorted. The two subquery columns won't sort. Even tho I'm not getting Errors.
Looking at the SQL-log shows that it's never even trying to order by those two columns...
Any ideas how to fix this/work around this are highly appreciated!
If you need more info about my code just leave a comment below.
EDIT 1:
As user #ndm pointed out, I had to put the computed fields into the sortWhitelist option of the pagination array.
Doing that worked out well as I was able to sort by the column headers:
$this->paginate = [
'limit' => '100',
'sortWhitelist' => [
'Locations.Area_ID',
'Unwanted_Software',
'Printers_Not_WHQL_Compatible'
],
'order' => ['Locations.Area_ID' => 'ASC']
]
But then the next problem appeared. Was trying to sort by the Printers_Not_WHQL_Compatible column. The generated SQL code had one small issue (BTW I'm using SQL Server 2008):
SELECT *
FROM (SELECT Locations.Area_ID AS [Locations__Area_ID],
(SELECT (COUNT(*)) FROM computers Computers WHERE (...)) AS [Unwanted_Software],
(SELECT (COUNT(*)) FROM printers Printers WHERE (...)) AS [Printers_Not_WHQL_Compatible],
(ROW_NUMBER() OVER (ORDER BY SELECT (COUNT(*)) FROM printers Printers WHERE (...) asc, Locations.Area_ID ASC)) AS [_cake_page_rownum_] FROM locations_Views Locations GROUP BY Locations.Area_ID ) _cake_paging_
WHERE _cake_paging_._cake_page_rownum_ <= 100
This represents the generated SQL code. Problem is that in de Order By statement there are no brackets around my subquery. It should look like this, so that SQL Server can read it:
... ORDER BY ( SELECT (COUNT(*)) FROM printers Printers WHERE (...) ) asc, ...
Any ideas how to fix this?
EDIT 2:
So #ndm answer with either the pull-request or the fix via the newExpr() function both work great. At least regarding the parentheses for the subquery in the Order By.
Sadly already running into the next problem. The generated SQL (this counts for both solutions!) is kinda "refreshing" the parameter input for the entire query in the Order By, which means that it puts the filter parameters for the Where clauses in starting again by parameter :c0. You can see that in the following query:
SELECT *
FROM (SELECT Locations.Area_ID AS [Locations__Area_ID],
(SELECT (COUNT(*))
FROM computers Computers
WHERE (Computers.Unwanted_Software != :c0
AND Computers.Area_ID = (Locations.Area_ID)
)
) AS [Unwanted_Software],
(SELECT (COUNT(*))
FROM printers Printers
WHERE (Printers.WHQL = :c1
AND Printers.Area_ID = (Locations.Area_ID))
) AS [Printers_Not_WHQL_Compatible],
(ROW_NUMBER() OVER (ORDER BY
(SELECT (COUNT(*))
FROM printers Printers
WHERE (Printers.WHQL = :c0
AND Printers.Area_ID = (Locations.Area_ID))) asc,
Locations.Area_ID ASC)
) AS [_cake_page_rownum_]
FROM locations_Views Locations
GROUP BY Locations.Area_ID ) _cake_paging_
WHERE _cake_paging_._cake_page_rownum_ <= 100
I don't think this is the intended result. I personally can probably work around that, by avoiding passing parameters directly (I don't have to deal with concrete external user input). Still think this should be looked over.
Thanks #ndm for great help! Definitely an upvote from me.
As linked in the comments, by default the paginator only allows sorting on columns that exist in the main table, columns of other tables (joins) or computed columns must be explicitly allowed via the sortWhiteList option.
The missing parentheses in the the generated window function SQL looks like a bug, the SQL Server query translator doesn't check whether the expression that is defined in the order clause is a query, which would require to wrap the generated SQL in parentheses.
I've pushed a possible fix for 3.9 and 4.1:
https://github.com/cakephp/cakephp/pull/15165
https://github.com/cakephp/cakephp/pull/15164
If you cannot upgrade right now, a possible workaround could be to wrap your subqueries in additional expressions, which when compiled should wrap the inner query expression in parentheses:
$dataQuery
->select([
'Locations.Area_ID',
'Unwanted_Software' => $dataQuery->newExpr($computersQuery),
'Printers_Not_WHQL_Compatible' => $dataQuery->newExpr($printersQuery)
])
->group('Locations.Area_ID');

How do I create a single measure, that slices the same data 3 different ways and unions it?

I've been trying for a day and a half now to figure out how to combine the same measure, in two different ways, in the same measure. It's been broken into parts, I've tried to UNION them, calculate with IF statements, I even thought I could UNION 3 summary tables to get the right output. I'm stuck using Excel 365 ProPlus (which I believe to be 2016 since Get and Transform and PowerPivot are built in).
The goal: I need to do this so that I can trick a PowerPivot table connected to the data model into displaying a) running total with b) a total line with c) a flat, non-running total Goal/Target line in the same measure. I've been able to do a & b, however c is elusive.
I tried to calculate the data in stages, with the first two steps here being that no matter what I try I can't seem to get two filters to work at the same time:
Occbase:=CALCULATE([Occurrences],
FILTER('Final Dataset',
'Final Dataset'[MainFilter] = ""))
CumOcc:=CALCULATE([Occbase],
FILTER(ALL(DimDate[DateValue]),
DimDate[DateValue] <= MAX(DimDate[DateValue])))
These two measures will do part 1, filter the dataset, and then calculate from that filter a simple running total. I've tried to do it in a single step but if the filter is working, then the running total won't work:
CombinedMakesRunningTotolStopWorking:=CALCULATE(SUM('Final Dataset'[xOccurrences]), FILTER(
ALL(Dimdate[DateValue]),
DimDate[DateValue] <= MAX(DimDate[DateValue]))
,FILTER(
'Final Dataset',
'Final Dataset'[MainFilter] = ""
|| 'Final Dataset'[Region] = "Ttl Occ MPR" //I couldn't figure out how to calculate on the fly
) //so I generated this total in PowerQuery
)
The SQL dev in me decided to try to pull all three above separately and then use UNION and SUMMARIZE by the date value and the region value but received an even worse result...
TryHarder:=SUMX(UNION(
SUMMARIZE(FILTER('Final Dataset',
'Final Dataset'[Region] = "Ttl Occ MPR"),
[Region],
[DateValue],
"OccurrencesXXX", CALCULATE([Occbase],
FILTER(ALL(DimDate[DateValue]),
DimDate[DateValue] <= MAX(DimDate[DateValue]))))
,
SUMMARIZE(FILTER(ALL('Final Dataset'),
'Final Dataset'[Region] = "PR Occ Goal"),
[Region],
[DateValue],
"OccurrencesXXX", [Occurrences])
,
SUMMARIZE(FILTER('Final Dataset',
'Final Dataset'[MainFilter] = ""),
[Region],
[DateValue],
"OccurrencesXXX", CALCULATE([Occbase],
FILTER(ALL(DimDate[DateValue]),
DimDate[DateValue] <= MAX(DimDate[DateValue]))))
), [OccurrencesXXX])
With the comically defeating result of:
I could give up and just generate a table for each chart in PowerQuery... but would have to generate a ton of tables. I have to assume I'm doing something wrong with scope/context and I have a feeling my C#/SQL mindset is putting me at a huge disadvantage in learning DAX. I'd like to understand what I'm doing wrong and learn the DAX pattern and terminology to fix it.
One way to do this is to setup a table that is not connected to the model, and then use that to determine what value you return. Example below being for a unit of measure (UOM). The idea being that the measure returned is dependent on the Unit of measure field, so adding it to the legend part of the pivot chart would return unit, case and ESU volume. It also means you could use a slicer to toggle which fields are returned in the chart.
Volume:=IF( HASONEVALUE( 'Unit of Measure'[UOM] ),
SWITCH(TRUE(),
VALUES('Unit of Measure'[Order]) = 1, [Unit Volume],
VALUES('Unit of Measure'[Order]) = 2, [Case Volume],
VALUES('Unit of Measure'[Order]) = 3, [ESU Volume]
),
[ESU Volume]
)

Laravel cashier + Stripe : creating a plan with quantity and decreasing prices

I'm whiling for one of my project to create a subscription system with Laravel Cashier and Stripe.
We will offer one plan to our users : 10€ / month for one location (they can add locations in the system) and for 75 followers.
What I want to do, is to make them pay more for locations : 2.5€ / locations / month for example, so this can be achieve with quantities ? But still, if the basic plan is at 10€ and I put 2 as a quantity, total price will be 20€ ?
Then price will be also based on their followers. 75 are included in the basic price. But then if they want more, they will also have to pay.
Example :
76-150 : + 4.95€ a month
151-250 : + 4.80€ a month etc ...
How can I handle that and make sure the customer will have to pay everything in one shot ?
Thanks in advance !
My advice would be to;
Calculate the total charge in your own logic,
Initiate a 'once-off' payment by first creating a customer object,
Then creating a charge!
Easy as 1,2,3 :D
Here's a tutorial from Stripes documentation on creating payments.
https://stripe.com/docs/charges
If you would like to add the user to a plan (subscription), see the below example (in PHP).
$customer = \Stripe\Customer::create(array(
"source" => $tokenID,
"plan" => $plan,
"email" => $email,
"coupon" => $coupon
));
I would use my front or back end to calculate:
price
discount rate
When the calculation is done, you can create your subscription with the right quantity and price, and discount rate (discount coupon).
$user->newSubscription('main', 'main')
->quantity($quantity)
->withCoupon($coupon)
->create($token, ['email' => $user->email]);

Finding out when an order status has been set to completed

I'm selling virtual products in my store where my customers buy a subscription for sth. This subscription is valid for 31 days beginning with the day when the order has been completed.
How do I find out the day when the order status had been set to completed?
My Idea was to look for the "updated_at" field of an order but I'm unsure if this is the right way to determine when the order was completed.
$sales_model = Mage::getModel("sales/order");
$all_orders = $sales_model -> getCollection()
->addFieldToFilter('status', 'complete')
->addAttributeToFilter('updated_at', array(
'from' => $one_month_ago,
'to' => date('Y-m-d 24:00:00'),
'date' => true,
)
);
The way I would approach this, is by create a custom module the keep track of the order status using observer
See Magento order status change events
Then log (in a db) the order # and date when the order was complete and expire. This way you could easily run report on when subscription will expire
Customize Magento using Event/Observe

Resources