InvoiceController::store()   D
last analyzed

Complexity

Conditions 15
Paths 240

Size

Total Lines 116
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 15
eloc 79
c 3
b 0
f 0
nc 240
nop 1
dl 0
loc 116
rs 4.1247

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Interfaces\InvoicingInterface;
6
use App\Models\Book;
7
use App\Models\Comment;
8
use App\Models\Config;
9
use App\Models\Discount;
10
use App\Models\Enrollment;
11
use App\Models\Fee;
12
use App\Models\Invoice;
13
use App\Models\InvoiceDetail;
14
use App\Models\InvoiceType;
15
use App\Models\Payment;
16
use App\Models\Paymentmethod;
17
use App\Models\ScheduledPayment;
18
use App\Models\Tax;
19
use Carbon\Carbon;
20
use Exception;
21
use Illuminate\Http\Request;
22
use Illuminate\Support\Facades\App;
23
use Illuminate\Support\Facades\Log;
24
use LaravelDaily\Invoices\Classes\Buyer;
25
use LaravelDaily\Invoices\Classes\InvoiceItem;
26
use LaravelDaily\Invoices\Invoice as InvoiceAlias;
27
28
class InvoiceController extends Controller
29
{
30
    public function __construct(public InvoicingInterface $invoicingService)
31
    {
32
        parent::__construct();
33
        $this->middleware(['permission:enrollments.edit']);
34
    }
35
36
    public function accountingServiceStatus()
37
    {
38
        return $this->invoicingService->status();
39
    }
40
41
    public function create()
42
    {
43
        if (config('invoicing.price_categories_enabled')) {
44
            abort(403, 'Unable to create an invoice because price categories are enabled in your setup.');
45
        }
46
47
        return view('carts.show', ['enrollment' => null,
48
            'products' => [],
49
            'invoicetypes' => InvoiceType::all(),
50
            'clients' => [],
51
            'availableBooks' => Book::all(),
52
            'availableFees' => Fee::all(),
53
            'availableDiscounts' => Discount::all(),
54
            'availableTaxes' => Tax::all(),
55
            'availablePaymentMethods' => Paymentmethod::all(), ]);
56
    }
57
58
    /**
59
     * Create a invoice based on the cart contents for the specified user
60
     * Receive in the request: the user ID + the invoice data.
61
     */
62
    public function store(Request $request)
63
    {
64
65
        // receive the client data and create a invoice with status = pending
66
        $invoice = Invoice::create([
67
            'client_name' => $request->client_name,
68
            'client_idnumber' => $request->client_idnumber,
69
            'client_address' => $request->client_address,
70
            'client_email' => $request->client_email,
71
            'client_phone' => $request->client_phone,
72
            'invoice_type_id' => $request->invoicetype,
73
            'receipt_number' => $request->receiptnumber,
74
            'date' => $request->has('date') ? Carbon::parse($request->date) : Carbon::now(),
75
        ]);
76
77
        $invoice->setNumber(); // TODO extract this to model events.
78
79
        // persist the products
80
        foreach ($request->products as $f => $product) {
81
            $productType = match ($product['type']) {
82
                'enrollment' => Enrollment::class,
83
                'scheduledPayment' => ScheduledPayment::class,
84
                'fee' => Fee::class,
85
                'book' => Book::class,
86
            };
87
88
            $productFinalPrice = 0; // used to compute the final price with taxes and discounts
89
90
            $productFinalPrice += $product['price'] * ($product['quantity'] ?? 1) * 100;
91
92
            // The front end sends the discounts value as percent, but  for the invoice we want to store their actual value relative to the product they were applied on
93
            if (isset($product['discounts'])) {
94
                foreach ($product['discounts'] as $d => $discount) {
95
                    InvoiceDetail::create([
96
                        'invoice_id' => $invoice->id,
97
                        'product_name' => $discount['name'],
98
                        'product_id' => $discount['id'],
99
                        'product_type' => Discount::class,
100
                        'price' => -($discount['value'] / 100) * $product['price'] * ($product['quantity'] ?? 1),
101
                    ]);
102
103
                    $productFinalPrice -= (($discount['value']) * $product['price']) * ($product['quantity'] ?? 1); // no need to multiply by 100 because discount is in %
104
                }
105
            }
106
107
            if (isset($product['taxes'])) {
108
                foreach ($product['taxes'] as $d => $tax) {
109
                    $productFinalPrice += (($tax['value']) * $product['price']) * ($product['quantity'] ?? 1); // no need to multiply by 100 because discount is in %
110
111
                    InvoiceDetail::create([
112
                        'invoice_id' => $invoice->id,
113
                        'product_name' => $tax['name'],
114
                        'product_id' => $tax['id'],
115
                        'product_type' => Tax::class,
116
                        'price' => $product['price'] * ($tax['value'] / 100) * ($product['quantity'] ?? 1),
117
                    ]);
118
                }
119
            }
120
121
            InvoiceDetail::create([
122
                'invoice_id' => $invoice->id,
123
                'product_name' => $product['name'],
124
                'product_code' => $product['product_code'],
125
                'product_id' => $product['id'],
126
                'product_type' => $productType,
127
                'price' => $product['price'],
128
                'final_price' => $productFinalPrice,
129
                'quantity' => $product['quantity'] ?? 1,
130
            ]);
131
        }
132
133
        foreach ($request->payments as $p => $payment) {
134
            Payment::create([
135
                'responsable_id' => backpack_user()->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
136
                'invoice_id' => $invoice->id,
137
                'payment_method' => $payment['method'] ?? null,
138
                'value' => $payment['value'],
139
                'date' => isset($payment['date']) ? Carbon::parse($payment['date']) : Carbon::now(),
140
            ]);
141
        }
142
143
        // send the details to Accounting
144
        // and receive and store the invoice number
145
        if ($request->sendinvoice == true && config('invoicing.invoicing_system')) {
146
            try {
147
                $invoiceNumber = $this->invoicingService->saveInvoice($invoice);
148
                Log::info($invoiceNumber);
149
                if ($invoiceNumber !== null) {
150
                    $invoice->receipt_number = $invoiceNumber;
151
                    $invoice->save();
152
                    $success = true;
153
                } else {
154
                    Invoice::where('id', $invoice->id)->delete();
155
                    abort(500);
156
                }
157
            } catch (Exception $exception) {
158
                Log::error('Data could not be sent to accounting');
159
                Log::error($exception);
160
            }
161
        } else {
162
            $success = true;
163
        }
164
        if (isset($success)) {
165
            $this->ifTheInvoiceIsFullyPaidMarkItsProductsAsSuch($invoice);
166
167
            if (isset($request->comment)) {
168
                Comment::create([
169
                    'commentable_id' => $invoice->id,
170
                    'commentable_type' => Invoice::class,
171
                    'body' => $request->comment,
172
                    'author_id' => backpack_user()->id,
173
                ]);
174
            }
175
        } else {
176
            Invoice::where('id', $invoice->id)->delete();
177
            abort(500);
178
        }
179
    }
180
181
    public function download(Invoice $invoice)
182
    {
183
        App::setLocale(config('app.locale'));
184
185
        $customer = new Buyer([
186
            'name' => $invoice->client_name,
187
            'custom_fields' => [
188
                'nif/cif' => $invoice->client_idnumber,
189
                'domicilio' => $invoice->client_address,
190
                'email' => $invoice->client_email,
191
            ],
192
        ]);
193
194
        $notes = $invoice->invoiceType->notes;
195
196
        $currencyFormat = config('app.currency_position') === 'before' ? '{SYMBOL}{VALUE}' : '{VALUE}{SYMBOL}';
197
        $generatedInvoice = InvoiceAlias::make()
198
            ->buyer($customer)
199
            ->series($invoice->invoice_series)
200
            ->sequence($invoice->invoice_number ?? $invoice->id)
201
            ->dateFormat('d/m/Y')
202
            ->date($invoice->date)
0 ignored issues
show
Bug introduced by
$invoice->date of type string is incompatible with the type Carbon\Carbon expected by parameter $date of LaravelDaily\Invoices\Invoice::date(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

202
            ->date(/** @scrutinizer ignore-type */ $invoice->date)
Loading history...
203
            ->logo(storage_path('logo2.png'))
204
            ->currencySymbol(config('app.currency_symbol'))
205
            ->currencyCode(config('app.currency_code'))
206
            ->currencyFormat($currencyFormat)
207
            ->notes($notes ?? '');
208
209
        foreach ($invoice->invoiceDetails as $product) {
210
            $item = (new InvoiceItem())->title($product->product_name)->pricePerUnit($product->price)->quantity($product->quantity);
211
212
            $generatedInvoice->addItem($item);
213
        }
214
215
        $generatedInvoice->footer = Config::firstWhere('name', 'invoice_footer')->value ?? '';
0 ignored issues
show
Bug introduced by
The property footer does not seem to exist on LaravelDaily\Invoices\Invoice.
Loading history...
216
217
        return $generatedInvoice->stream();
218
    }
219
220
    public function savePayments(Request $request, Invoice $invoice)
221
    {
222
        $invoice->payments()->delete();
223
224
        foreach ($request->payments as $payment) {
225
            $invoice->payments()->create([
226
                'payment_method' => $payment['payment_method'] ?? null,
227
                'value' => $payment['value'],
228
                'date' => isset($payment['date']) ? Carbon::parse($payment['date']) : Carbon::now(),
229
                'status' => $payment['status'] ?? 1,
230
                'responsable_id' => backpack_user()->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
231
            ]);
232
        }
233
234
        $this->ifTheInvoiceIsFullyPaidMarkItsProductsAsSuch($invoice);
235
236
        return $invoice->fresh()->payments;
237
    }
238
239
    private function ifTheInvoiceIsFullyPaidMarkItsProductsAsSuch(Invoice $invoice): void
240
    {
241
        foreach ($invoice->scheduledPayments as $scheduledPayment) {
242
            if ($invoice->totalPrice() === $invoice->paidTotal()) {
243
                $scheduledPayment->product->markAsPaid();
244
245
                /** @var Enrollment $relatedEnrollment */
246
                $relatedEnrollment = $scheduledPayment->product->enrollment;
247
                if ($relatedEnrollment && $relatedEnrollment->scheduledPayments->where('status', '!==', 2)->count() === 0) {
248
                    $relatedEnrollment->markAsPaid();
249
                }
250
            }
251
        }
252
253
        foreach ($invoice->enrollments as $enrollment) {
254
            if ($invoice->totalPrice() === $invoice->paidTotal() && $enrollment->product->price == $enrollment->product->total_paid_price) {
255
                $enrollment->product->markAsPaid();
256
            }
257
        }
258
    }
259
}
260