diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php index 2b43bee73c..ca4f727e80 100644 --- a/app/Api/V1/Controllers/BillController.php +++ b/app/Api/V1/Controllers/BillController.php @@ -23,9 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; use Auth; +use Carbon\Carbon; use FireflyIII\Models\Bill; -use FireflyIII\Transformers\BillTransformer; +use FireflyIII\Transformers\Bill\BillTransformer; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; @@ -57,17 +59,25 @@ class BillController extends Controller */ public function index(Request $request) { - $user = Auth::guard('api')->user(); - $pageSize = intval(Preferences::getForUser($user, 'listPageSize', 50)->data); + $user = Auth::guard('api')->user(); + $pageSize = intval(Preferences::getForUser($user, 'listPageSize', 50)->data); + $start = null; + $end = null; + if (null !== $request->get('start')) { + $start = new Carbon($request->get('start')); + } + if (null !== $request->get('end')) { + $end = new Carbon($request->get('end')); + } $paginator = $user->bills()->paginate($pageSize); - $bills = $paginator->getCollection(); + /** @var Collection $bills */ + $bills = $paginator->getCollection(); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - - $resource = new FractalCollection($bills, new BillTransformer(), 'bills'); + $resource = new FractalCollection($bills, new BillTransformer($start, $end), 'bills'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/AttachmentTransformer.php similarity index 51% rename from app/Transformers/BillTransformer.php rename to app/Transformers/AttachmentTransformer.php index e5cf812658..1d6540bdda 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -1,6 +1,6 @@ (int)$bill->id, - 'name' => $bill->name, - 'match' => explode(',', $bill->match), - 'amount_min' => round($bill->amount_min, 2), - 'amount_max' => round($bill->amount_max, 2), - 'date' => $bill->date->format('Y-m-d'), - 'repeat_freq' => $bill->repeat_freq, - 'skip' => (int)$bill->skip, - 'automatch' => intval($bill->automatch) === 1, - 'active' => intval($bill->active) === 1, - 'links' => [ - [ - 'rel' => 'self', - 'uri' => '/bills/' . $bill->id, - ], - ], + 'id' => (int)$attachment->id, ]; } + } \ No newline at end of file diff --git a/app/Transformers/Bill/BillTransformer.php b/app/Transformers/Bill/BillTransformer.php new file mode 100644 index 0000000000..4431912e2f --- /dev/null +++ b/app/Transformers/Bill/BillTransformer.php @@ -0,0 +1,196 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers\Bill; + +use Carbon\Carbon; +use FireflyIII\Models\Bill; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use Illuminate\Support\Collection; +use League\Fractal\TransformerAbstract; + +/** + * Class BillTransformer + */ +class BillTransformer extends TransformerAbstract +{ + /** @var Carbon */ + private $end = null; + /** @var Carbon */ + private $start = null; + + /** + * BillTransformer constructor. + * + * @param Carbon|null $start + * @param Carbon|null $end + */ + public function __construct(Carbon $start = null, Carbon $end = null) + { + $this->start = $start; + $this->end = $end; + } + + /** + * @param Bill $bill + * + * @return array + */ + public function transform(Bill $bill): array + { + $paidData = $this->paidData($bill); + $data = [ + 'id' => (int)$bill->id, + 'name' => $bill->name, + 'match' => explode(',', $bill->match), + 'amount_min' => round($bill->amount_min, 2), + 'amount_max' => round($bill->amount_max, 2), + 'date' => $bill->date->format('Y-m-d'), + 'repeat_freq' => $bill->repeat_freq, + 'skip' => (int)$bill->skip, + 'automatch' => intval($bill->automatch) === 1, + 'active' => intval($bill->active) === 1, + 'attachments_count' => $bill->attachments()->count(), + 'pay_dates' => $this->payDates($bill), + 'paid_dates' => $paidData['paid_dates'], + 'next_expected_match' => $paidData['next_expected_match'], + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/bills/' . $bill->id, + ], + ], + ]; + + + return $data; + + } + + /** + * Returns the latest date in the set, or start when set is empty. + * + * @param Collection $dates + * @param Carbon $default + * + * @return Carbon + */ + protected function lastPaidDate(Collection $dates, Carbon $default): Carbon + { + if (0 === $dates->count()) { + return $default; // @codeCoverageIgnore + } + $latest = $dates->first(); + /** @var Carbon $date */ + foreach ($dates as $date) { + if ($date->gte($latest)) { + $latest = $date; + } + } + + return $latest; + } + + /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction. Whether or not it is there already, is not relevant. + * + * @param Bill $bill + * @param Carbon $date + * + * @return \Carbon\Carbon + */ + protected function nextDateMatch(Bill $bill, Carbon $date): Carbon + { + $start = clone $bill->date; + while ($start < $date) { + $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); + } + + return $start; + } + + /** + * @param Bill $bill + * + * @return array + */ + protected function paidData(Bill $bill): array + { + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $repository->setUser($bill->user); + $set = $repository->getPaidDatesInRange($bill, $this->start, $this->end); + $simple = $set->map( + function (Carbon $date) { + return $date->format('Y-m-d'); + } + ); + + // calculate next expected match: + $lastPaidDate = $this->lastPaidDate($set, $this->start); + $nextMatch = clone $bill->date; + while ($nextMatch < $lastPaidDate) { + $nextMatch = app('navigation')->addPeriod($nextMatch, $bill->repeat_freq, $bill->skip); + } + $end = app('navigation')->addPeriod($nextMatch, $bill->repeat_freq, $bill->skip); + $journalCount = $repository->getPaidDatesInRange($bill, $nextMatch, $end)->count(); + if ($journalCount > 0) { + $nextMatch = clone $end; + } + + return [ + 'paid_dates' => $simple->toArray(), + 'next_expected_match' => $nextMatch->format('Y-m-d'), + ]; + } + + /** + * @param Bill $bill + * + * @return array + */ + protected function payDates(Bill $bill): array + { + $set = new Collection; + $currentStart = clone $this->start; + while ($currentStart <= $this->end) { + $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + // If nextExpectedMatch is after end, we continue: + if ($nextExpectedMatch > $this->end) { + break; + } + // add to set + $set->push(clone $nextExpectedMatch); + $nextExpectedMatch->addDay(); + $currentStart = clone $nextExpectedMatch; + } + $simple = $set->map( + function (Carbon $date) { + return $date->format('Y-m-d'); + } + ); + + return $simple->toArray(); + } +} \ No newline at end of file