• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kcal-app / kcal / 8953420469

04 May 2024 09:37PM UTC coverage: 92.994% (-0.4%) from 93.391%
8953420469

push

github

cdubz
Correct PHPMA config

1407 of 1513 relevant lines covered (92.99%)

14.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.89
/app/Http/Controllers/JournalEntryController.php
1
<?php
2
/**
3
 * @noinspection PhpDocSignatureInspection
4
 */
5

6
namespace App\Http\Controllers;
7

8
use App\Http\Requests\StoreFromNutrientsJournalEntryRequest;
9
use App\Http\Requests\StoreJournalEntryRequest;
10
use App\Models\Food;
11
use App\Models\Goal;
12
use App\Models\JournalDate;
13
use App\Models\JournalEntry;
14
use App\Models\Recipe;
15
use App\Support\ArrayFormat;
16
use App\Support\Number;
17
use App\Support\Nutrients;
18
use Illuminate\Contracts\View\View;
19
use Illuminate\Http\RedirectResponse;
20
use Illuminate\Http\Request;
21
use Illuminate\Support\Carbon;
22
use Illuminate\Support\Facades\Auth;
23
use Illuminate\Support\Pluralizer;
24
use Illuminate\Support\Str;
25

26
class JournalEntryController extends Controller
27
{
28
    /**
29
     * Display a listing of the resource.
30
     */
31
    public function index(Request $request): View
32
    {
33
        $date = $request->date ?? Carbon::now()->toDateString();
2✔
34
        $date = Carbon::rawCreateFromFormat('Y-m-d', $date);
2✔
35

36
        // Get entries and nutrient sums for the day.
37
        $entries = JournalEntry::where([
2✔
38
            'user_id' => Auth::user()->id,
2✔
39
            'date' => $date->toDateString(),
2✔
40
        ])->get();
2✔
41
        $sums = [];
2✔
42
        foreach (Nutrients::all()->pluck('value') as $nutrient) {
2✔
43
            $sums[$nutrient] = round($entries->sum($nutrient));
2✔
44
        }
45

46
        // Get daily goals data for user.
47
        $goal = Auth::user()->getGoalByDate($date);
2✔
48
        $goalProgress = [];
2✔
49
        if ($goal) {
2✔
50
            foreach (Nutrients::all()->pluck('value') as $nutrient) {
2✔
51
                if ($goal->{$nutrient} > 0) {
2✔
52
                    $goalProgress[$nutrient] = round($sums[$nutrient] / $goal->{$nutrient} * 100);
2✔
53
                    $goalProgress[$nutrient] .= '%';
2✔
54
                }
55
            }
56
        }
57

58
        // Get all goals as options to change for the date.
59
        $goalOptions = Goal::whereUserId(Auth::user()->id)
2✔
60
            ->orderBy('name')
2✔
61
            ->get()
2✔
62
            ->map(function (Goal $goal) {
2✔
63
                return ['value' => $goal->id, 'label' => $goal->name];
2✔
64
            });
2✔
65

66
        // Get the associated journal date.
67
        // @todo Refactor journal date as a relationship on journal entries.
68
        $journalDate = JournalDate::getOrCreateJournalDate(Auth::user(), $date);
2✔
69

70
        return view('journal-entries.index')
2✔
71
            ->with('entries', $entries)
2✔
72
            ->with('sums', $sums)
2✔
73
            ->with('currentGoal', $goal)
2✔
74
            ->with('goalProgress', $goalProgress)
2✔
75
            ->with('goalOptions', $goalOptions)
2✔
76
            ->with('journalDate', $journalDate)
2✔
77
            ->with('date', $date);
2✔
78
    }
79

80
    /**
81
     * Show the form for creating a new resource.
82
     */
83
    public function create(Request $request): View
84
    {
85
        $date = $request->date ?? Carbon::now()->toDateString();
2✔
86
        $ingredients = [];
2✔
87
        if ($old = old('ingredients')) {
2✔
88
            foreach ($old['amount'] as $key => $amount) {
1✔
89
                if (
90
                    empty($old['date'][$key])
1✔
91
                    && empty($old['meal'][$key])
1✔
92
                    && empty($amount)
1✔
93
                    && empty($old['unit'][$key])
1✔
94
                    && empty($old['id'][$key])
1✔
95
                ) {
96
                    continue;
×
97
                }
98
                $ingredients[$key] = [
1✔
99
                    'key' => $key,
1✔
100
                    'date' => $old['date'][$key],
1✔
101
                    'meal' => $old['meal'][$key],
1✔
102
                    'amount' => $amount,
1✔
103
                    'unit' => $old['unit'][$key],
1✔
104
                    'id' => $old['id'][$key],
1✔
105
                    'type' => $old['type'][$key],
1✔
106
                    'name' => $old['name'][$key],
1✔
107
                ];
1✔
108

109
                // Add supported units for the ingredient.
110
                $ingredient = NULL;
1✔
111
                if ($ingredients[$key]['type'] === Food::class) {
1✔
112
                    $ingredient = Food::whereId($ingredients[$key]['id'])->first();
1✔
113
                }
114
                elseif ($ingredients[$key]['type'] === Recipe::class) {
1✔
115
                    $ingredient = Recipe::whereId($ingredients[$key]['id'])->first();
1✔
116
                }
117
                if ($ingredient) {
1✔
118
                    $ingredients[$key]['units'] = $ingredient->units_supported;
1✔
119
                }
120
            }
121
        }
122

123
        return view('journal-entries.create')
2✔
124
            ->with('ingredients', $ingredients)
2✔
125
            ->with('units', Nutrients::units()->toArray())
2✔
126
            ->with('default_date', Carbon::createFromFormat('Y-m-d', $date));
2✔
127
    }
128

129
    /**
130
     * Show the form for creating a journal entry from nutrients directly.
131
     */
132
    public function createFromNutrients(Request $request): View
133
    {
134
        $date = $request->date ?? Carbon::now()->toDateString();
1✔
135
        return view('journal-entries.create-from-nutrients')
1✔
136
            ->with('units', Nutrients::units()->toArray())
1✔
137
            ->with('default_date', Carbon::createFromFormat('Y-m-d', $date));
1✔
138
    }
139

140
    /**
141
     * Store a newly created resource in storage.
142
     */
143
    public function store(StoreJournalEntryRequest $request): RedirectResponse
144
    {
145
        $input = $request->validated();
2✔
146

147
        $ingredients = ArrayFormat::flipTwoDimensionalKeys($input['ingredients']);
2✔
148

149
        /** @var \App\Models\JournalEntry[] $entries */
150
        $entries = [];
2✔
151
        $entry_key = 0;
2✔
152
        $group_entries = isset($input['group_entries']) && (bool) $input['group_entries'];
2✔
153
        // TODO: Improve efficiency. Potential for lots of queries here...
154
        foreach ($ingredients as $ingredient) {
2✔
155
            // Set entry key (combined date and meal or individual entries).
156
            if ($group_entries) {
2✔
157
                $entry_key = "{$ingredient['date']}{$ingredient['meal']}";
1✔
158
            }
159
            else {
160
                $entry_key++;
1✔
161
            }
162

163
            // Get an existing entry (when grouping) or create a new one.
164
            $entries[$entry_key] = $entries[$entry_key] ?? JournalEntry::make([
2✔
165
                'date' => $ingredient['date'],
2✔
166
                'meal' => $ingredient['meal'],
2✔
167
            ])->user()->associate(Auth::user());
2✔
168
            $entry = &$entries[$entry_key];
2✔
169

170
            // Calculate amounts based on ingredient type.
171
            $item = NULL;
2✔
172
            $amount = Number::floatFromString($ingredient['amount']);
2✔
173
            if ($ingredient['type'] == Food::class) {
2✔
174
                $item = Food::whereId($ingredient['id'])->first();
2✔
175
                $nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier($item, $amount, $ingredient['unit']);
2✔
176
                foreach (Nutrients::all()->pluck('value') as $nutrient) {
2✔
177
                    $entry->{$nutrient} += $item->{$nutrient} * $nutrient_multiplier;
2✔
178
                }
179
                $entry->foods->add($item);
2✔
180
            }
181
            elseif ($ingredient['type'] == Recipe::class) {
1✔
182
                $item = Recipe::whereId($ingredient['id'])->first();
1✔
183
                foreach (Nutrients::all()->pluck('value') as $nutrient) {
1✔
184
                    $entry->{$nutrient} += Nutrients::calculateRecipeNutrientAmount($item, $nutrient, $amount, $ingredient['unit']);
1✔
185
                }
186
                $entry->recipes->add($item);
1✔
187
            }
188

189
            // Add to summary.
190
            if (!empty($entry->summary)) {
2✔
191
                $entry->summary .= '; ';
×
192
            }
193
            $entry->summary .= $this->createIngredientSummary($ingredient, $item, $amount);
2✔
194
        }
195

196
        // Save all new entries.
197
        foreach ($entries as $new_entry) {
2✔
198
            $new_entry->save();
2✔
199
            $new_entry->user->save();
2✔
200
            $new_entry->foods()->saveMany($new_entry->foods);
2✔
201
            $new_entry->recipes()->saveMany($new_entry->recipes);
2✔
202
        }
203

204
        $count = count($entries);
2✔
205
        session()->flash('message', "Added {$count} journal entries!");
2✔
206

207
        // Redirect to the date if only one date is used.
208
        $parameters = [];
2✔
209
        $unique_dates = array_unique($input['ingredients']['date']);
2✔
210
        if (count($unique_dates) === 1) {
2✔
211
            $parameters['date'] = reset($unique_dates);
×
212
        }
213
        return redirect()->route('journal-entries.index', $parameters);
2✔
214
    }
215

216
    /**
217
     * Attempt to create a coherent summary for an entry ingredient.
218
     */
219
    private function createIngredientSummary(array $ingredient, Food|Recipe $item, float $amount): string {
220
        $name = $item->name;
2✔
221
        $unit = $ingredient['unit'];
2✔
222

223
        // Determine unit with special handling for custom Food units.
224
        if ($item instanceof Food) {
2✔
225
            if ($unit === 'serving') {
2✔
226
                $no_serving_unit = empty($item->serving_unit) && empty($item->serving_unit_name);
×
227

228
                // If there is no serving unit or the serving unit name is
229
                // exactly the same as the item name don't use a serving
230
                // unit and pluralize the _item_ name.
231
                if ($no_serving_unit || $item->serving_unit_name === $name) {
×
232
                    $unit = null;
×
233
                    $name = Pluralizer::plural($name, $amount);
×
234
                }
235

236
                // If the serving unit name is already _part_ of the item
237
                // name, just keep the defined unit (e.g. name: "tortilla
238
                // chips" and serving name "chips").
239
                elseif (Str::contains($name, $item->serving_unit_name)) {
×
240
                    $unit = 'serving';
×
241
                }
242

243
                // If a serving unit name is set, use the formatted serving
244
                // unit name as a base.
245
                elseif (!empty($item->serving_unit_name)) {
×
246
                    $unit = $item->serving_unit_formatted;
×
247
                }
248
            }
249
        }
250

251
        // Pluralize unit with supplied plurals or Pluralizer.
252
        if (Nutrients::units()->has($unit)) {
2✔
253
            $value = 'label';
2✔
254
            if ($amount > 1) {
2✔
255
                $value = 'plural';
2✔
256
            }
257
            $unit = Nutrients::units()->get($unit)[$value];
2✔
258
        }
259
        else {
260
            $unit = Pluralizer::plural($unit, $amount);
×
261
        }
262

263
        // Add amount, unit, and name to summary.
264
        $amount = Number::rationalStringFromFloat($amount);
2✔
265
        $summary = "{$amount} {$unit} {$name}";
2✔
266

267
        // Add detail if available.
268
        if (isset($item->detail) && !empty($item->detail)) {
2✔
269
            $summary .= ", {$item->detail}";
2✔
270
        }
271

272
        return $summary;
2✔
273
    }
274

275
    /**
276
     * Store an entry from nutrients.
277
     */
278
    public function storeFromNutrients(StoreFromNutrientsJournalEntryRequest $request): RedirectResponse {
279
        $attributes = $request->validated();
1✔
280
        $entry = JournalEntry::make(array_filter($attributes, function ($value) {
1✔
281
            return !is_null($value);
1✔
282
        }))->user()->associate(Auth::user());
1✔
283
        $entry->save();
1✔
284
        session()->flash('message', "Journal entry added!");
1✔
285
        return redirect()->route(
1✔
286
            'journal-entries.index',
1✔
287
            ['date' => $entry->date->format('Y-m-d')]
1✔
288
        );
1✔
289
    }
290

291
    /**
292
     * Confirm removal of the specified resource.
293
     */
294
    public function delete(JournalEntry $journal_entry): View
295
    {
296
        return view('journal-entries.delete')
1✔
297
            ->with('journal_entry', $journal_entry);
1✔
298
    }
299

300
    /**
301
     * Remove the specified resource from storage.
302
     */
303
    public function destroy(JournalEntry $journal_entry): RedirectResponse
304
    {
305
        $journal_entry->delete();
1✔
306
        session()->flash('message', 'Journal entry deleted!');
1✔
307
        return redirect(route('journal-entries.index', [
1✔
308
            'date' => $journal_entry->date->toDateString()
1✔
309
        ]));
1✔
310
    }
311
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc