Merge branch 'master' into uat
[readifood.git] / lib / report.php
1 <?php
2
3   if (isset($_POST['show_reports'])) {
4     header(sprintf("Location: http%s://%s/%s/from/%s/to/%s", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, $_POST['from'], $_POST['to']));
5     exit;
6   }
7
8   function show_reports_form($from = null, $to = null) {
9     form("noprint standout");
10     echo "<p>Show reports covering the period from ";
11
12     /* Default to last month. */
13     list($y, $m, $d) = explode('-', date('Y-m-d', time()));
14     $latest = "$y-$m-$d";
15     $now = mktime(0, 0, 0, $m, $d, $y);
16     $first = mktime(0, 0, 0, $m, 1, $y);
17     $last = $first - 86400;
18     $date = date('Y-m-d', $last);
19     if (is_null($to)) $to = $date;
20     list($y, $m, $d) = explode('-', $date);
21     $first = mktime(0, 0, 0, $m, 1, $y);
22     if (is_null($from)) $from = date('Y-m-d', $first);
23     $date = $first;
24     for ($i = 0; $i < 2; $i++) {
25       $date -= 86400;
26       list ($y, $m, $d) = explode('-', date('Y-m-d', $date));
27       $date = mktime(0, 0, 0, $m, 1, $y);
28     }
29     $oldest = date('Y-m-d', $date);
30     $then = $date;
31
32     datepicker("from", $from, true, null, false, "to");
33     echo " to ";
34     datepicker("to", $to, true, "from", false);
35
36     submit("show_reports", "Show");
37     end_form();
38   }
39
40   function check_report_dates($from, $to) {
41     list($y, $m, $d) = explode('-', $from);
42     if (! checkdate($m, $d, $y)) {
43       echo "<p>Invalid report start date!</p>\n";
44       return false;
45     }
46     $start = mktime(0, 0, 0, $m, $d, $y);
47
48     list($y, $m, $d) = explode('-', $to);
49     if (! checkdate($m, $d, $y)) {
50       echo "<p>Invalid report end date!</p>\n";
51       return false;
52     }
53     $end = mktime(0, 0, 0, $m, $d, $y);
54
55     if ($end < $start) {
56       echo "<p>Report end date is earlier than start date!</p>\n";
57       return false;
58     }
59
60     return true;
61   }
62
63   function show_order_report($from, &$order_state_ids) {
64     echo "<h3>Orders by week</h3>\n";
65
66     /* Handle missing weeks at the start of the range. */
67     $dbh = Propel::getConnection();
68     $sth = $dbh->prepare("select yearweek(:from)");
69     $sth->execute(array(':from' => $from));
70     list($first_week) = $sth->fetch();
71     $year_offset = substr($first_week, 0, 4);
72     $week_offset = substr($first_week, 4, 2) - 1;
73
74     $q = new OrderStateQuery;
75     $q->filterById($order_state_ids);
76     $q->withColumn('yearweek(updated)', 'week');
77     $q->withColumn('count(*)', 'count');
78     $q->addGroupByColumn('week')->orderByUpdated();
79     $rows = $q->find();
80     $week = 1;
81     $last_week = 0;
82     $total = 0;
83     echo "<table class=\"report\">\n";
84     foreach ($rows as $row) {
85       /*
86         Convert week of year to date range.
87         Beware that week 201401 comes after 201352.
88       */
89       $yearweek = $row->getWeek();
90       $y = substr($yearweek, 0, 4);
91       $w = substr($yearweek, 4, 2);
92       $week = (($y - $year_offset) * 52) + ($w - $week_offset);
93       $total += $row->getCount();
94       /* Fill in missing weeks. XXX */
95       for ($missing_week = $last_week + 1; $missing_week < $week; $missing_week++) {
96         echo "<tr>\n";
97         printf("  <td align=\"right\">0</td><td>Week %d</td>\n", $missing_week);
98         echo "</tr>\n";
99       }
100       echo "<tr>\n";
101       printf("  <td align=\"right\">%d</td><td>Week %d</td>\n", $row->getCount(), $week);
102       echo "</tr>\n";
103       $last_week = $week;
104     }
105     echo "<tr>\n";
106     echo "  <td align=\"right\" class=\"strong\">$total</td><td class=\"strong\">Total</td>\n";
107     echo "</tr>\n";
108     echo "</table>\n";
109   }
110
111   function show_postcode_report(&$order_ids) {
112     echo "<h3>Orders by postcode</h3>\n";
113
114     /*
115       No regex replace support in MySQL so we'll have to retrieve all records
116       and group the postcodes ourselves.
117     */
118     $q = new OrderQuery;
119     $q->filterById($order_ids);
120     $q->join("Beneficiary");
121     /* No foreign key so we need to list the two tables. */
122     $q->join("Beneficiary.Address");
123     /* Not a FoodOrder column so we need to ask for it explicitly. */
124     $q->withColumn('upper(postcode)', 'postcode');
125     $rows = $q->find();
126
127     $total = 0;
128     $postcodes = array();
129     foreach ($rows as $row) {
130       $postcode = preg_replace('/\s*[0-9][A-Z]+$/', '', trim($row->getPostcode()));
131       if (! $postcode) $postcode = "Unknown";
132       $postcodes[$postcode]++;
133       $total++;
134     }
135     ksort($postcodes);
136
137     echo "<table class=\"report\">\n";
138     foreach ($postcodes as $postcode => $count) {
139       echo "<tr>\n";
140       printf("  <td align=\"right\">%d</td><td>%s</td>\n", $count, htmlspecialchars($postcode));
141       echo "</tr>\n";
142     }
143     echo "<tr>\n";
144     echo "  <td align=\"right\" class=\"strong\">$total</td><td class=\"strong\">Total</td>\n";
145     echo "</tr>\n";
146     echo "</table>\n";
147   }
148
149   function show_contents_report(&$order_ids, $parcel_size, $grand_total) {
150     global $parcel_sizes, $parcel_contents;
151
152     $total = 0;
153     for ($i = count($parcel_sizes); $i < count($parcel_contents); $i++) {
154       $q = new OrderQuery;
155       $q->filterById($order_ids);
156       $q->where(sprintf("parcel & %d", $parcel_size));
157       $q->where(sprintf("parcel & %d", (1 << $i)));
158       $contents = $q->find();
159       $total += count($contents);
160       echo "<tr class=\"small\">\n";
161       printf("  <td align=\"right\">%d</td><td>%s</td>\n", count($contents), htmlspecialchars($parcel_contents[$i]));
162       echo "</tr>\n";
163     }
164
165     /* No special contents. */
166     echo "<tr class=\"small\">\n";
167     printf("  <td align=\"right\">%d</td><td>%s no special contents</td>\n", $grand_total - $total, htmlspecialchars($parcel_sizes[$parcel_size >> 1]));
168     echo "</tr>\n";
169   }
170
171   function show_parcel_report(&$order_ids) {
172     global $parcel_sizes;
173     echo "<h3>Orders by parcel type</h3>\n";
174
175     $q = new OrderQuery;
176     $q->filterById($order_ids);
177     $q->withColumn(sprintf("parcel & %d", (1 << count($parcel_sizes)) - 1), 'size');
178     $q->withColumn('count(*)', 'count');
179     $q->addGroupByColumn('size')->addAscendingOrderByColumn('size');
180     $rows = $q->find();
181     $total = 0;
182     echo "<table class=\"report\">\n";
183     foreach ($rows as $row) {
184       echo "<tr>\n";
185       $requester = get_contact_by_id($row->getRequesterId());
186       printf("  <td align=\"right\">%d</td><td>%s</td>\n", $row->getCount(), htmlspecialchars($parcel_sizes[$row->getSize() >> 1]));
187       $total += $row->getCount();
188       echo "</tr>\n";
189       show_contents_report($order_ids, $row->getSize(), $row->getCount());
190     }
191     echo "<tr>\n";
192     echo "  <td align=\"right\" class=\"strong\">$total</td><td class=\"strong\">Total</td>\n";
193     echo "</tr>\n";
194     echo "</table>\n";
195   }
196
197   function show_requester_report(&$order_ids) {
198     echo "<h3>Orders by referrer</h3>\n";
199
200     $q = new OrderQuery;
201     $q->filterById($order_ids);
202     $q->withColumn('count(*)', 'count');
203     $q->groupByRequesterId()->addDescendingOrderByColumn('count');
204     $rows = $q->find();
205     $total = 0;
206     echo "<table class=\"report\">\n";
207     /* XXX Join! */
208     foreach ($rows as $row) {
209       echo "<tr>\n";
210       $requester = get_contact_by_id($row->getRequesterId());
211       printf("  <td align=\"right\">%d</td><td>%s</td>\n", $row->getCount(), htmlspecialchars($requester->getDisplayname()));
212       $total += $row->getCount();
213       echo "</tr>\n";
214     }
215     echo "<tr>\n";
216     echo "  <td align=\"right\" class=\"strong\">$total</td><td class=\"strong\">Total</td>\n";
217     echo "</tr>\n";
218     echo "</table>\n";
219   }
220
221   function show_reports($from, $to) {
222     if (! check_report_dates($from, $to)) return;
223
224     echo "<p>Showing reports for the period <strong>$from</strong> to <strong>$to</strong>.</p>\n";
225
226     /* Get orders. */
227     $order_ids = array();
228     $order_state_ids = array();
229     /* XXX: Order 51 changed to state delivered in May then updated in June. */
230     $dbh = Propel::getConnection();
231     $sth = $dbh->prepare("select * from OrderState o where updated=(select min(updated) from OrderState where order_id=o.order_id and state & " . $GLOBALS['STATE_DELIVERED'] . ") and updated between '$from' and '$to'");
232     $sth->execute();
233     $order_states = OrderStatePeer::populateObjects($sth);
234     $dups = array();
235     foreach ($order_states as $order_state) {
236       $order_id = $order_state->getOrderId();
237       $order_ids[] = $order_id;
238       if (! $dups[$order_id]) $order_state_ids[] = $order_state->getId();
239       $dups[$order_id] = true;
240     }
241     $q = new OrderQuery;
242     $q->filterById($order_ids);
243
244     if (! count($order_ids)) {
245       echo "<p>No results!</p>\n";
246       return;
247     }
248
249     show_order_report($from, $order_state_ids);
250     show_postcode_report($order_ids);
251     show_parcel_report($order_ids);
252     show_requester_report($order_ids);
253   }
254
255   if (count($parameters)) {
256     if ($parameters[0] == "from") {
257       $from = $parameters[1];
258       if ($parameters[2] == "to") $to = $parameters[3];
259       show_reports($from, $to);
260     }
261   }
262   show_reports_form($from, $to);
263 ?>