Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 556 |
|
0.00% |
0 / 34 |
CRAP | |
0.00% |
0 / 1 |
ApiBillController | |
0.00% |
0 / 556 |
|
0.00% |
0 / 34 |
9312 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
apiBillUpdate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
validateBillUpdate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
updateBillFromRequest | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
apiBillCreate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
createBillDatabaseEntry | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
createBaseBill | |
0.00% |
0 / 56 |
|
0.00% |
0 / 1 |
156 | |||
createBaseBillElement | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
createBillFromRequest | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
validateBillCreate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
apiMediaAddToBill | |
0.00% |
0 / 74 |
|
0.00% |
0 / 1 |
90 | |||
apiMediaRemoveFromBill | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
30 | |||
validateMediaRemoveFromBill | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
createBillDir | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
validateMediaAddToBill | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
apiBillElementCreate | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
6 | |||
createBillElementFromRequest | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
validateBillElementCreate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiMediaRender | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
apiPreviewRender | |
0.00% |
0 / 88 |
|
0.00% |
0 / 1 |
30 | |||
apiBillPdfArchiveCreate | |
0.00% |
0 / 112 |
|
0.00% |
0 / 1 |
72 | |||
sendBillEmail | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
2 | |||
apiNoteCreate | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
validateNoteCreate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiBillDelete | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
deleteBillFromRequest | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
validateBillDelete | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiBillElementUpdate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
updateBillElementFromRequest | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateBillElementUpdate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiBillElementDelete | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
validateBillElementDelete | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiNoteUpdate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
apiNoteDelete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package Modules\Billing |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace Modules\Billing\Controller; |
16 | |
17 | use Modules\Admin\Models\NullAccount; |
18 | use Modules\Admin\Models\SettingsEnum as AdminSettingsEnum; |
19 | use Modules\Billing\Models\Bill; |
20 | use Modules\Billing\Models\BillElement; |
21 | use Modules\Billing\Models\BillElementMapper; |
22 | use Modules\Billing\Models\BillMapper; |
23 | use Modules\Billing\Models\BillStatus; |
24 | use Modules\Billing\Models\BillTypeMapper; |
25 | use Modules\Billing\Models\NullBill; |
26 | use Modules\Billing\Models\NullBillElement; |
27 | use Modules\Billing\Models\SettingsEnum; |
28 | use Modules\ClientManagement\Models\Client; |
29 | use Modules\ClientManagement\Models\ClientMapper; |
30 | use Modules\ItemManagement\Models\Item; |
31 | use Modules\ItemManagement\Models\ItemMapper; |
32 | use Modules\Media\Models\CollectionMapper; |
33 | use Modules\Media\Models\Media; |
34 | use Modules\Media\Models\MediaMapper; |
35 | use Modules\Media\Models\PathSettings; |
36 | use Modules\Media\Models\UploadStatus; |
37 | use Modules\Messages\Models\EmailMapper; |
38 | use Modules\SupplierManagement\Models\NullSupplier; |
39 | use Modules\SupplierManagement\Models\Supplier; |
40 | use Modules\SupplierManagement\Models\SupplierMapper; |
41 | use phpOMS\Application\ApplicationAbstract; |
42 | use phpOMS\Autoloader; |
43 | use phpOMS\Localization\ISO4217CharEnum; |
44 | use phpOMS\Localization\ISO639x1Enum; |
45 | use phpOMS\Message\Http\RequestStatusCode; |
46 | use phpOMS\Message\Mail\Email; |
47 | use phpOMS\Message\NotificationLevel; |
48 | use phpOMS\Message\RequestAbstract; |
49 | use phpOMS\Message\ResponseAbstract; |
50 | use phpOMS\Model\Message\FormValidation; |
51 | use phpOMS\System\MimeType; |
52 | use phpOMS\Views\View; |
53 | |
54 | /** |
55 | * Billing class. |
56 | * |
57 | * @package Modules\Billing |
58 | * @license OMS License 2.0 |
59 | * @link https://jingga.app |
60 | * @since 1.0.0 |
61 | */ |
62 | final class ApiBillController extends Controller |
63 | { |
64 | /** |
65 | * Constructor. |
66 | * |
67 | * @param null|ApplicationAbstract $app Application instance |
68 | * |
69 | * @since 1.0.0 |
70 | */ |
71 | public function __construct(ApplicationAbstract $app = null) |
72 | { |
73 | parent::__construct($app); |
74 | |
75 | if ($this->app->moduleManager->isActive('WarehouseManagement')) { |
76 | $this->app->eventManager->importFromFile(__DIR__ . '/../../WarehouseManagement/Admin/Hooks/Manual.php'); |
77 | } |
78 | } |
79 | |
80 | /** |
81 | * Api method to update a bill |
82 | * |
83 | * @param RequestAbstract $request Request |
84 | * @param ResponseAbstract $response Response |
85 | * @param array $data Generic data |
86 | * |
87 | * @return void |
88 | * |
89 | * @api |
90 | * |
91 | * @since 1.0.0 |
92 | */ |
93 | public function apiBillUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
94 | { |
95 | if (!empty($val = $this->validateBillUpdate($request))) { |
96 | $response->header->status = RequestStatusCode::R_400; |
97 | $this->createInvalidUpdateResponse($request, $response, $val); |
98 | |
99 | return; |
100 | } |
101 | |
102 | /** @var \Modules\Billing\Models\Bill $old */ |
103 | $old = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute(); |
104 | $new = $this->updateBillFromRequest($request, clone $old); |
105 | |
106 | $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin()); |
107 | $this->createStandardUpdateResponse($request, $response, $new); |
108 | } |
109 | |
110 | /** |
111 | * Method to validate bill creation from request |
112 | * |
113 | * @param RequestAbstract $request Request |
114 | * |
115 | * @return array<string, bool> |
116 | * |
117 | * @since 1.0.0 |
118 | */ |
119 | private function validateBillUpdate(RequestAbstract $request) : array |
120 | { |
121 | $val = []; |
122 | if (($val['bill'] = !$request->hasData('bill'))) { |
123 | return $val; |
124 | } |
125 | |
126 | return []; |
127 | } |
128 | |
129 | /** |
130 | * Method to create a bill from request. |
131 | * |
132 | * @param RequestAbstract $request Request |
133 | * @param Bill $old Bill |
134 | * |
135 | * @return Bill |
136 | * |
137 | * @since 1.0.0 |
138 | */ |
139 | public function updateBillFromRequest(RequestAbstract $request, Bill $old) : Bill |
140 | { |
141 | return $old; |
142 | } |
143 | |
144 | /** |
145 | * Api method to create a bill |
146 | * |
147 | * @param RequestAbstract $request Request |
148 | * @param ResponseAbstract $response Response |
149 | * @param array $data Generic data |
150 | * |
151 | * @return void |
152 | * |
153 | * @api |
154 | * |
155 | * @since 1.0.0 |
156 | */ |
157 | public function apiBillCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
158 | { |
159 | if (!empty($val = $this->validateBillCreate($request))) { |
160 | $response->header->status = RequestStatusCode::R_400; |
161 | $this->createInvalidCreateResponse($request, $response, $val); |
162 | |
163 | return; |
164 | } |
165 | |
166 | $bill = $this->createBillFromRequest($request, $response, $data); |
167 | $this->createBillDatabaseEntry($bill, $request); |
168 | $this->createStandardCreateResponse($request, $response, $bill); |
169 | } |
170 | |
171 | /** |
172 | * Create a new database entry for a Bill object and update its bill number |
173 | * |
174 | * @param Bill $bill The Bill object to create a database entry for and update its bill number |
175 | * @param RequestAbstract $request The request object that contains the header account and origin |
176 | * |
177 | * @return void |
178 | * |
179 | * @since 1.0.0 |
180 | */ |
181 | public function createBillDatabaseEntry(Bill $bill, RequestAbstract $request) : void |
182 | { |
183 | $this->createModel($request->header->account, $bill, BillMapper::class, 'bill', $request->getOrigin()); |
184 | |
185 | // We ned to get the bill again since the bill has a trigger which is executed on insert |
186 | // @todo: consider to remove the trigger and select the latest bill here and add + 1 to the new sequence since we have to tdo an update anyways |
187 | /** @var Bill $bill */ |
188 | $tmp = BillMapper::get() |
189 | ->where('id', $bill->id) |
190 | ->execute(); |
191 | |
192 | $bill->sequence = $tmp->sequence; |
193 | |
194 | $old = clone $bill; |
195 | $bill->buildNumber(); // The bill id is part of the number |
196 | $this->updateModel($request->header->account, $old, $bill, BillMapper::class, 'bill', $request->getOrigin()); |
197 | } |
198 | |
199 | /** |
200 | * Create a base Bill object with default values |
201 | * |
202 | * @param Client|Supplier $account The client or supplier object for whom the bill is being created |
203 | * @param RequestAbstract $request The request object that contains the header account |
204 | * |
205 | * @return Bill The new Bill object with default values |
206 | * |
207 | * @todo Validate VAT before creation (maybe need to add a status when last validated, we don't want to validate every time) |
208 | * @todo Set the correct date of payment |
209 | * @todo Use bill and shipping address instead of main address if available |
210 | * @todo Implement allowed invoice languages and a default invoice language if none match |
211 | * @todo Implement client invoice language (allowing for different invoice languages than invoice address) |
212 | * |
213 | * @since 1.0.0 |
214 | */ |
215 | public function createBaseBill(Client | Supplier $account, RequestAbstract $request) : Bill |
216 | { |
217 | // @todo: validate vat before creation for clients |
218 | $bill = new Bill(); |
219 | $bill->createdBy = new NullAccount($request->header->account); |
220 | $bill->unit = $account->unit ?? $this->app->unitId; |
221 | $bill->billDate = new \DateTime('now'); // @todo: Date of payment |
222 | $bill->performanceDate = $request->getDataDateTime('performancedate') ?? new \DateTime('now'); // @todo: Date of payment |
223 | $bill->accountNumber = $account->number; |
224 | $bill->setStatus($request->getDataInt('status') ?? BillStatus::DRAFT); |
225 | |
226 | $bill->shipping = 0; |
227 | $bill->shippingText = ''; |
228 | |
229 | $bill->payment = 0; |
230 | $bill->paymentText = ''; |
231 | |
232 | if ($account instanceof Client) { |
233 | $bill->client = $account; |
234 | } else { |
235 | $bill->supplier = $account; |
236 | } |
237 | |
238 | // @todo: use bill and shipping address instead of main address if available |
239 | $bill->billTo = $request->getDataString('billto') ?? $account->account->name1; |
240 | $bill->billAddress = $request->getDataString('billaddress') ?? $account->mainAddress->address; |
241 | $bill->billCity = $request->getDataString('billtocity') ?? $account->mainAddress->city; |
242 | $bill->billZip = $request->getDataString('billtopostal') ?? $account->mainAddress->postal; |
243 | $bill->billCountry = $request->getDataString('billtocountry') ?? $account->mainAddress->getCountry(); |
244 | |
245 | $bill->setCurrency(ISO4217CharEnum::_EUR); |
246 | |
247 | /** @var \Model\Setting $settings */ |
248 | $settings = $this->app->appSettings->get(null, |
249 | SettingsEnum::VALID_BILL_LANGUAGES, |
250 | unit: $this->app->unitId, |
251 | module: 'Admin' |
252 | ); |
253 | |
254 | if (empty($settings)) { |
255 | /** @var \Model\Setting $settings */ |
256 | $settings = $this->app->appSettings->get(null, |
257 | SettingsEnum::VALID_BILL_LANGUAGES, |
258 | unit: null, |
259 | module: 'Admin' |
260 | ); |
261 | } |
262 | |
263 | $validLanguages = []; |
264 | if (!empty($settings) && !empty($settings->content)) { |
265 | $validLanguages = \json_decode($settings->content, true); |
266 | } |
267 | |
268 | if (empty($validLanguages) || !\is_array($validLanguages)) { |
269 | $validLanguages = [ |
270 | ISO639x1Enum::_EN, |
271 | ]; |
272 | } |
273 | |
274 | $billLanguage = $validLanguages[0] ?? ISO639x1Enum::_EN; |
275 | |
276 | $accountBillLanguage = $account->getAttribute('bill_language')->value->valueStr; |
277 | if (!empty($accountBillLanguage) && \in_array($accountBillLanguage, $validLanguages)) { |
278 | $billLanguage = $accountBillLanguage; |
279 | } else { |
280 | $accountLanguages = ISO639x1Enum::languageFromCountry($account->mainAddress->getCountry()); |
281 | $accountLanguage = empty($accountLanguages) ? '' : $accountLanguages[0]; |
282 | |
283 | if (\in_array($accountLanguage, $validLanguages)) { |
284 | $billLanguage = $accountLanguage; |
285 | } |
286 | } |
287 | |
288 | $bill->language = $billLanguage; |
289 | |
290 | $typeMapper = BillTypeMapper::get() |
291 | ->with('l11n') |
292 | ->where('l11n/langauge', $billLanguage) |
293 | ->limit(1); |
294 | |
295 | if ($request->hasData('type')) { |
296 | $typeMapper->where('id', $request->getDataInt('type')); |
297 | } else { |
298 | $typeMapper->where('name', 'sales_invoice'); |
299 | } |
300 | |
301 | $bill->type = $typeMapper->execute(); |
302 | |
303 | return $bill; |
304 | } |
305 | |
306 | /** |
307 | * Create a base BillElement object with default values |
308 | * |
309 | * @param Client $client The client object for whom the bill is being created |
310 | * @param Item $item The item object for which the bill element is being created |
311 | * @param Bill $bill The bill object for which the bill element is being created |
312 | * @param RequestAbstract $request The request object that contains the header account |
313 | * |
314 | * @return BillElement |
315 | * |
316 | * @since 1.0.0 |
317 | */ |
318 | public function createBaseBillElement(Client $client, Item $item, Bill $bill, RequestAbstract $request) : BillElement |
319 | { |
320 | $taxCode = $this->app->moduleManager->get('Billing', 'ApiTax') |
321 | ->getTaxCodeFromClientItem($client, $item, $request->header->l11n->country); |
322 | |
323 | return BillElement::fromItem( |
324 | $item, |
325 | $taxCode, |
326 | $request->getDataInt('quantity') ?? 1, |
327 | $bill->id |
328 | ); |
329 | } |
330 | |
331 | /** |
332 | * Method to create a bill from request. |
333 | * |
334 | * @param RequestAbstract $request Request |
335 | * @param ResponseAbstract $response Response |
336 | * @param array $data Generic data |
337 | * |
338 | * @return Bill |
339 | * |
340 | * @since 1.0.0 |
341 | */ |
342 | public function createBillFromRequest(RequestAbstract $request, ResponseAbstract $response, $data = null) : Bill |
343 | { |
344 | /** @var \Modules\ClientManagement\Models\Client|\Modules\SupplierManagement\Models\Supplier $account */ |
345 | $account = null; |
346 | if ($request->hasData('client')) { |
347 | /** @var \Modules\ClientManagement\Models\Client $account */ |
348 | $account = ClientMapper::get() |
349 | ->with('account') |
350 | ->with('mainAddress') |
351 | ->where('id', (int) $request->getData('client')) |
352 | ->execute(); |
353 | } elseif (($request->getDataInt('supplier') ?? -1) === 0) { |
354 | /** @var \Modules\SupplierManagement\Models\Supplier $account */ |
355 | $account = new NullSupplier(); |
356 | } elseif ($request->hasData('supplier')) { |
357 | /** @var \Modules\SupplierManagement\Models\Supplier $account */ |
358 | $account = SupplierMapper::get() |
359 | ->with('account') |
360 | ->with('mainAddress') |
361 | ->where('id', (int) $request->getData('supplier')) |
362 | ->execute(); |
363 | } |
364 | |
365 | return $this->createBaseBill($account, $request); |
366 | } |
367 | |
368 | /** |
369 | * Method to validate bill creation from request |
370 | * |
371 | * @param RequestAbstract $request Request |
372 | * |
373 | * @return array<string, bool> |
374 | * |
375 | * @since 1.0.0 |
376 | */ |
377 | private function validateBillCreate(RequestAbstract $request) : array |
378 | { |
379 | $val = []; |
380 | if (($val['client/supplier'] = (!$request->hasData('client') |
381 | && (!$request->hasData('supplier') |
382 | && ($request->getDataInt('supplier') ?? -1) !== 0) |
383 | )) |
384 | || ($val['type'] = (!$request->hasData('type'))) |
385 | ) { |
386 | return $val; |
387 | } |
388 | |
389 | return []; |
390 | } |
391 | |
392 | /** |
393 | * Api method to add Media to a Bill |
394 | * |
395 | * @param RequestAbstract $request Request |
396 | * @param ResponseAbstract $response Response |
397 | * @param array $data Generic data |
398 | * |
399 | * @return void |
400 | * |
401 | * @api |
402 | * |
403 | * @since 1.0.0 |
404 | */ |
405 | public function apiMediaAddToBill(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
406 | { |
407 | if (!empty($val = $this->validateMediaAddToBill($request))) { |
408 | $response->header->status = RequestStatusCode::R_400; |
409 | $this->createInvalidUpdateResponse($request, $response, $val); |
410 | |
411 | return; |
412 | } |
413 | |
414 | /** @var \Modules\Billing\Models\Bill $bill */ |
415 | $bill = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute(); |
416 | $path = $this->createBillDir($bill); |
417 | |
418 | $uploaded = []; |
419 | if (!empty($uploadedFiles = $request->files)) { |
420 | $uploaded = $this->app->moduleManager->get('Media')->uploadFiles( |
421 | names: [], |
422 | fileNames: [], |
423 | files: $uploadedFiles, |
424 | account: $request->header->account, |
425 | basePath: __DIR__ . '/../../../Modules/Media/Files' . $path, |
426 | virtualPath: $path, |
427 | pathSettings: PathSettings::FILE_PATH, |
428 | hasAccountRelation: false, |
429 | readContent: $request->getDataBool('parse_content') ?? false |
430 | ); |
431 | |
432 | $collection = null; |
433 | foreach ($uploaded as $media) { |
434 | $this->createModelRelation( |
435 | $request->header->account, |
436 | $bill->id, |
437 | $media->id, |
438 | BillMapper::class, |
439 | 'files', |
440 | '', |
441 | $request->getOrigin() |
442 | ); |
443 | |
444 | if ($request->hasData('type')) { |
445 | $this->createModelRelation( |
446 | $request->header->account, |
447 | $media->id, |
448 | $request->getDataInt('type'), |
449 | MediaMapper::class, |
450 | 'types', |
451 | '', |
452 | $request->getOrigin() |
453 | ); |
454 | } |
455 | |
456 | if ($collection === null) { |
457 | /** @var \Modules\Media\Models\Collection $collection */ |
458 | $collection = MediaMapper::getParentCollection($path) |
459 | ->limit(1) |
460 | ->execute(); |
461 | |
462 | if ($collection->id === 0) { |
463 | $collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection( |
464 | $path, |
465 | $request->header->account, |
466 | __DIR__ . '/../../../Modules/Media/Files' . $path, |
467 | ); |
468 | } |
469 | } |
470 | |
471 | $this->createModelRelation( |
472 | $request->header->account, |
473 | $collection->id, |
474 | $media->id, |
475 | CollectionMapper::class, |
476 | 'sources', |
477 | '', |
478 | $request->getOrigin() |
479 | ); |
480 | } |
481 | } |
482 | |
483 | if (!empty($mediaFiles = $request->getDataJson('media'))) { |
484 | foreach ($mediaFiles as $media) { |
485 | $this->createModelRelation( |
486 | $request->header->account, |
487 | $bill->id, |
488 | (int) $media, |
489 | BillMapper::class, |
490 | 'files', |
491 | '', |
492 | $request->getOrigin() |
493 | ); |
494 | } |
495 | } |
496 | |
497 | $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Media', 'Media added to bill.', [ |
498 | 'upload' => $uploaded, |
499 | 'media' => $mediaFiles, |
500 | ]); |
501 | } |
502 | |
503 | /** |
504 | * Api method to remove Media from Bill |
505 | * |
506 | * @param RequestAbstract $request Request |
507 | * @param ResponseAbstract $response Response |
508 | * @param array $data Generic data |
509 | * |
510 | * @return void |
511 | * |
512 | * @api |
513 | * |
514 | * @since 1.0.0 |
515 | */ |
516 | public function apiMediaRemoveFromBill(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
517 | { |
518 | // @todo: check that it is not system generated media! |
519 | if (!empty($val = $this->validateMediaRemoveFromBill($request))) { |
520 | $response->header->status = RequestStatusCode::R_400; |
521 | $this->createInvalidDeleteResponse($request, $response, $val); |
522 | |
523 | return; |
524 | } |
525 | |
526 | /** @var \Modules\Media\Models\Media $media */ |
527 | $media = MediaMapper::get()->where('id', (int) $request->getData('media'))->execute(); |
528 | |
529 | /** @var \Modules\Billing\Models\Bill $bill */ |
530 | $bill = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute(); |
531 | |
532 | $path = $this->createBillDir($bill); |
533 | |
534 | /** @var \Modules\Media\Models\Collection[] */ |
535 | $billCollection = CollectionMapper::getAll() |
536 | ->where('virtual', $path) |
537 | ->execute(); |
538 | |
539 | if (\count($billCollection) !== 1) { |
540 | // For some reason there are multiple collections with the same virtual path? |
541 | // @todo: check if this is the correct way to handle it or if we need to make sure that it is a collection |
542 | return; |
543 | } |
544 | |
545 | $collection = \reset($billCollection); |
546 | |
547 | $this->deleteModelRelation( |
548 | $request->header->account, |
549 | $bill->id, |
550 | $media->id, |
551 | BillMapper::class, |
552 | 'files', |
553 | '', |
554 | $request->getOrigin() |
555 | ); |
556 | |
557 | $this->deleteModelRelation( |
558 | $request->header->account, |
559 | $collection->id, |
560 | $media->id, |
561 | CollectionMapper::class, |
562 | 'sources', |
563 | '', |
564 | $request->getOrigin() |
565 | ); |
566 | |
567 | $referenceCount = MediaMapper::countInternalReferences($media->id); |
568 | |
569 | if ($referenceCount === 0) { |
570 | // Is not used anywhere else -> remove from db and file system |
571 | |
572 | // @todo: remove media types from media |
573 | |
574 | $this->deleteModel($request->header->account, $media, MediaMapper::class, 'bill_media', $request->getOrigin()); |
575 | |
576 | if (\is_dir($media->getAbsolutePath())) { |
577 | \phpOMS\System\File\Local\Directory::delete($media->getAbsolutePath()); |
578 | } else { |
579 | \phpOMS\System\File\Local\File::delete($media->getAbsolutePath()); |
580 | } |
581 | } |
582 | |
583 | $this->createStandardDeleteResponse($request, $response, $media); |
584 | } |
585 | |
586 | /** |
587 | * Validate Media remove from Bill request |
588 | * |
589 | * @param RequestAbstract $request Request |
590 | * |
591 | * @return array<string, bool> |
592 | * |
593 | * @since 1.0.0 |
594 | */ |
595 | private function validateMediaRemoveFromBill(RequestAbstract $request) : array |
596 | { |
597 | $val = []; |
598 | if (($val['media'] = !$request->hasData('media')) |
599 | || ($val['bill'] = !$request->hasData('bill')) |
600 | ) { |
601 | return $val; |
602 | } |
603 | |
604 | return []; |
605 | } |
606 | |
607 | /** |
608 | * Create media directory path |
609 | * |
610 | * @param Bill $bill Bill |
611 | * |
612 | * @return string |
613 | * |
614 | * @since 1.0.0 |
615 | */ |
616 | private function createBillDir(Bill $bill) : string |
617 | { |
618 | return '/Modules/Billing/Bills/' |
619 | . $this->app->unitId . '/' |
620 | . $bill->createdAt->format('Y/m/d') . '/' |
621 | . $bill->id; |
622 | } |
623 | |
624 | /** |
625 | * Method to validate bill creation from request |
626 | * |
627 | * @param RequestAbstract $request Request |
628 | * |
629 | * @return array<string, bool> |
630 | * |
631 | * @since 1.0.0 |
632 | */ |
633 | private function validateMediaAddToBill(RequestAbstract $request) : array |
634 | { |
635 | $val = []; |
636 | if (($val['media'] = (!$request->hasData('media') && empty($request->files))) |
637 | || ($val['bill'] = !$request->hasData('bill')) |
638 | ) { |
639 | return $val; |
640 | } |
641 | |
642 | return []; |
643 | } |
644 | |
645 | /** |
646 | * Api method to create a bill element |
647 | * |
648 | * @param RequestAbstract $request Request |
649 | * @param ResponseAbstract $response Response |
650 | * @param array $data Generic data |
651 | * |
652 | * @return void |
653 | * |
654 | * @api |
655 | * |
656 | * @since 1.0.0 |
657 | */ |
658 | public function apiBillElementCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
659 | { |
660 | if (!empty($val = $this->validateBillElementCreate($request))) { |
661 | $response->header->status = RequestStatusCode::R_400; |
662 | $this->createInvalidCreateResponse($request, $response, $val); |
663 | |
664 | return; |
665 | } |
666 | |
667 | /** @var \Modules\Billing\Models\Bill $old */ |
668 | $old = BillMapper::get() |
669 | ->with('client') |
670 | ->with('client/attributes') |
671 | ->with('client/attributes/type') |
672 | ->with('client/attributes/value') |
673 | ->where('id', $request->getDataInt('bill') ?? 0) |
674 | ->execute(); |
675 | |
676 | $element = $this->createBillElementFromRequest($request, $response, $old, $data); |
677 | $this->createModel($request->header->account, $element, BillElementMapper::class, 'bill_element', $request->getOrigin()); |
678 | |
679 | // @todo: handle stock transaction here |
680 | // @todo: if transaction fails don't update below and send warning to user |
681 | // @todo: however mark transaction as reserved and only update when bill is finalized!!! |
682 | |
683 | // @todo: in BillElementUpdate do the same |
684 | |
685 | $new = clone $old; |
686 | $new->addElement($element); |
687 | |
688 | $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill_element', $request->getOrigin()); |
689 | $this->createStandardCreateResponse($request, $response, $element); |
690 | } |
691 | |
692 | /** |
693 | * Method to create a bill element from request. |
694 | * |
695 | * @param RequestAbstract $request Request |
696 | * @param ResponseAbstract $response Response |
697 | * @param Bill $bill Bill to create element for |
698 | * @param array $data Generic data |
699 | * |
700 | * @return BillElement |
701 | * |
702 | * @since 1.0.0 |
703 | */ |
704 | private function createBillElementFromRequest(RequestAbstract $request, ResponseAbstract $response, Bill $bill, $data = null) : BillElement |
705 | { |
706 | /** @var \Modules\ItemManagement\Models\Item $item */ |
707 | $item = ItemMapper::get() |
708 | ->with('attributes') |
709 | ->with('attributes/type') |
710 | ->with('attributes/value') |
711 | ->with('l11n') |
712 | ->with('l11n/type') |
713 | ->where('id', $request->getDataInt('item') ?? 0) |
714 | ->where('l11n/type/title', ['name1', 'name2', 'name3'], 'IN') |
715 | ->where('l11n/language', $bill->language) |
716 | ->execute(); |
717 | |
718 | if ($bill->client === null) { |
719 | return new NullBillElement(); |
720 | } |
721 | |
722 | $element = $this->createBaseBillElement($bill->client, $item, $bill, $request); |
723 | $element->bill = new NullBill($bill->id); |
724 | |
725 | // discounts |
726 | // @todo: implement a addDiscount function |
727 | /* |
728 | if ($request->getData('discount_percentage') !== null) { |
729 | } |
730 | */ |
731 | |
732 | return $element; |
733 | } |
734 | |
735 | /** |
736 | * Method to validate bill element creation from request |
737 | * |
738 | * @param RequestAbstract $request Request |
739 | * |
740 | * @return array<string, bool> |
741 | * |
742 | * @since 1.0.0 |
743 | */ |
744 | private function validateBillElementCreate(RequestAbstract $request) : array |
745 | { |
746 | $val = []; |
747 | if (($val['bill'] = !$request->hasData('bill'))) { |
748 | return $val; |
749 | } |
750 | |
751 | return []; |
752 | } |
753 | |
754 | /** |
755 | * Render bill media |
756 | * |
757 | * @param RequestAbstract $request Request |
758 | * @param ResponseAbstract $response Response |
759 | * @param array $data Generic data |
760 | * |
761 | * @return void |
762 | * |
763 | * @api |
764 | * |
765 | * @since 1.0.0 |
766 | */ |
767 | public function apiMediaRender(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
768 | { |
769 | // @todo: check if has permission |
770 | $this->app->moduleManager->get('Media', 'Api')->apiMediaExport($request, $response, ['ignorePermission' => true]); |
771 | } |
772 | |
773 | /** |
774 | * Api method to create a bill preview |
775 | * |
776 | * @param RequestAbstract $request Request |
777 | * @param ResponseAbstract $response Response |
778 | * @param array $data Generic data |
779 | * |
780 | * @return void |
781 | * |
782 | * @api |
783 | * |
784 | * @since 1.0.0 |
785 | */ |
786 | public function apiPreviewRender(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
787 | { |
788 | /** @var \Modules\Billing\Models\Bill $bill */ |
789 | $bill = BillMapper::get() |
790 | ->with('type') |
791 | ->with('type/l11n') |
792 | ->with('elements') |
793 | ->where('id', $request->getDataInt('bill') ?? 0) |
794 | ->execute(); |
795 | |
796 | Autoloader::addPath(__DIR__ . '/../../../Resources/'); |
797 | |
798 | $templateId = $request->getData('bill_template', 'int'); |
799 | if ($templateId === null) { |
800 | $billTypeId = $request->getData('bill_type', 'int'); |
801 | |
802 | if (empty($billTypeId)) { |
803 | $billTypeId = $bill->type->id; |
804 | } |
805 | |
806 | if (empty($billTypeId)) { |
807 | return; |
808 | } |
809 | |
810 | /** @var \Modules\Billing\Models\BillType $billType */ |
811 | $billType = BillTypeMapper::get() |
812 | ->with('defaultTemplate') |
813 | ->where('id', $billTypeId) |
814 | ->execute(); |
815 | |
816 | $templateId = $billType->defaultTemplate?->id; |
817 | } |
818 | |
819 | /** @var \Modules\Media\Models\Collection $template */ |
820 | $template = CollectionMapper::get() |
821 | ->with('sources') |
822 | ->where('id', $templateId) |
823 | ->execute(); |
824 | |
825 | require_once __DIR__ . '/../../../Resources/tcpdf/TCPDF.php'; |
826 | |
827 | $response->header->set('Content-Type', MimeType::M_PDF, true); |
828 | |
829 | $view = new View($this->app->l11nManager, $request, $response); |
830 | $view->setTemplate('/' . \substr($template->getSourceByName('bill.pdf.php')->getPath(), 0, -8), 'pdf.php'); |
831 | |
832 | /** @var \Model\Setting[] $settings */ |
833 | $settings = $this->app->appSettings->get(null, |
834 | [ |
835 | AdminSettingsEnum::DEFAULT_TEMPLATES, |
836 | AdminSettingsEnum::DEFAULT_ASSETS, |
837 | ], |
838 | unit: $this->app->unitId, |
839 | module: 'Admin' |
840 | ); |
841 | |
842 | if (empty($settings)) { |
843 | /** @var \Model\Setting[] $settings */ |
844 | $settings = $this->app->appSettings->get(null, |
845 | [ |
846 | AdminSettingsEnum::DEFAULT_TEMPLATES, |
847 | AdminSettingsEnum::DEFAULT_ASSETS, |
848 | ], |
849 | unit: null, |
850 | module: 'Admin' |
851 | ); |
852 | } |
853 | |
854 | /** @var \Modules\Media\Models\Collection $defaultTemplates */ |
855 | $defaultTemplates = CollectionMapper::get() |
856 | ->with('sources') |
857 | ->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_TEMPLATES]->content) |
858 | ->execute(); |
859 | |
860 | /** @var \Modules\Media\Models\Collection $defaultAssets */ |
861 | $defaultAssets = CollectionMapper::get() |
862 | ->with('sources') |
863 | ->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_ASSETS]->content) |
864 | ->execute(); |
865 | |
866 | $view->data['defaultTemplates'] = $defaultTemplates; |
867 | $view->data['defaultAssets'] = $defaultAssets; |
868 | |
869 | $path = $this->createBillDir($bill); |
870 | $pdfDir = __DIR__ . '/../../../Modules/Media/Files' . $path; |
871 | |
872 | $view->data['bill'] = $bill; |
873 | $view->data['path'] = $pdfDir . '/' . ($bill->billDate?->format('Y-m-d') ?? '0') . '_' . $bill->number . '.pdf'; |
874 | |
875 | $view->data['bill_creator'] = $request->getDataString('bill_creator'); |
876 | $view->data['bill_title'] = $request->getDataString('bill_title'); |
877 | $view->data['bill_subtitle'] = $request->getDataString('bill_subtitle'); |
878 | $view->data['keywords'] = $request->getDataString('keywords'); |
879 | |
880 | $view->data['bill_type_name'] = $request->getDataString('bill_type_name'); |
881 | |
882 | $view->data['bill_start_text'] = $request->getDataString('bill_start_text'); |
883 | $view->data['bill_lines'] = $request->getDataString('bill_lines'); |
884 | $view->data['bill_end_text'] = $request->getDataString('bill_end_text'); |
885 | |
886 | $view->data['bill_payment_terms'] = $request->getDataString('bill_payment_terms'); |
887 | $view->data['bill_terms'] = $request->getDataString('bill_terms'); |
888 | $view->data['bill_taxes'] = $request->getDataString('bill_taxes'); |
889 | $view->data['bill_currency'] = $request->getDataString('bill_currency'); |
890 | |
891 | // Unit specifc settings |
892 | $view->data['bill_logo_name'] = $request->getDataString('bill_logo_name'); |
893 | $view->data['bill_slogan'] = $request->getDataString('bill_slogan'); |
894 | $view->data['legal_company_name'] = $request->getDataString('legal_company_name'); |
895 | $view->data['bill_company_address'] = $request->getDataString('bill_company_address'); |
896 | $view->data['bill_company_city'] = $request->getDataString('bill_company_city'); |
897 | $view->data['bill_company_ceo'] = $request->getDataString('bill_company_ceo'); |
898 | $view->data['bill_company_website'] = $request->getDataString('bill_company_website'); |
899 | $view->data['bill_company_email'] = $request->getDataString('bill_company_email'); |
900 | $view->data['bill_company_phone'] = $request->getDataString('bill_company_phone'); |
901 | $view->data['bill_company_terms'] = $request->getDataString('bill_company_terms'); |
902 | |
903 | $view->data['bill_company_tax_office'] = $request->getDataString('bill_company_tax_office'); |
904 | $view->data['bill_company_tax_id'] = $request->getDataString('bill_company_tax_id'); |
905 | $view->data['bill_company_vat_id'] = $request->getDataString('bill_company_vat_id'); |
906 | |
907 | $view->data['bill_company_bank_name'] = $request->getDataString('bill_company_bank_name'); |
908 | $view->data['bill_company_swift'] = $request->getDataString('bill_company_swift'); |
909 | $view->data['bill_company_bank_account'] = $request->getDataString('bill_company_bank_account'); |
910 | |
911 | $pdf = $view->render(); |
912 | |
913 | $response->set('', $pdf); |
914 | } |
915 | |
916 | /** |
917 | * Api method to create and archive a bill |
918 | * |
919 | * @param RequestAbstract $request Request |
920 | * @param ResponseAbstract $response Response |
921 | * @param array $data Generic data |
922 | * |
923 | * @return void |
924 | * |
925 | * @api |
926 | * |
927 | * @since 1.0.0 |
928 | */ |
929 | public function apiBillPdfArchiveCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
930 | { |
931 | Autoloader::addPath(__DIR__ . '/../../../Resources/'); |
932 | |
933 | /** @var \Modules\Billing\Models\Bill $bill */ |
934 | $bill = BillMapper::get() |
935 | ->where('id', $request->getDataInt('bill') ?? 0) |
936 | ->execute(); |
937 | |
938 | // @todo: This is stupid to do twice but I need to get the langauge. |
939 | // For the future it should just be a join on the bill langauge!!! |
940 | // The problem is the where here is a model where and not a query |
941 | // builder where meaning it is always considered a value and not a column. |
942 | |
943 | /** @var \Modules\Billing\Models\Bill $bill */ |
944 | $bill = BillMapper::get() |
945 | ->with('type') |
946 | ->with('type/l11n') |
947 | ->with('type/defaultTemplate') |
948 | ->with('elements') |
949 | ->where('id', $request->getDataInt('bill') ?? 0) |
950 | ->where('type/l11n/language', $bill->language) |
951 | ->execute(); |
952 | |
953 | $templateId = $request->getDataInt('bill_template'); |
954 | if ($templateId === null) { |
955 | $templateId = $bill->type->defaultTemplate?->id; |
956 | } |
957 | |
958 | /** @var \Modules\Media\Models\Collection $template */ |
959 | $template = CollectionMapper::get() |
960 | ->with('sources') |
961 | ->where('id', $templateId) |
962 | ->execute(); |
963 | |
964 | require_once __DIR__ . '/../../../Resources/tcpdf/TCPDF.php'; |
965 | |
966 | $view = new View($this->app->l11nManager, $request, $response); |
967 | $view->setTemplate('/' . \substr($template->getSourceByName('bill.pdf.php')->getPath(), 0, -8), 'pdf.php'); |
968 | |
969 | /** @var \Model\Setting[] $settings */ |
970 | $settings = $this->app->appSettings->get(null, |
971 | [ |
972 | AdminSettingsEnum::DEFAULT_TEMPLATES, |
973 | AdminSettingsEnum::DEFAULT_ASSETS, |
974 | ], |
975 | unit: $this->app->unitId, |
976 | module: 'Admin' |
977 | ); |
978 | |
979 | if (empty($settings)) { |
980 | /** @var \Model\Setting[] $settings */ |
981 | $settings = $this->app->appSettings->get(null, |
982 | [ |
983 | AdminSettingsEnum::DEFAULT_TEMPLATES, |
984 | AdminSettingsEnum::DEFAULT_ASSETS, |
985 | ], |
986 | unit: null, |
987 | module: 'Admin' |
988 | ); |
989 | } |
990 | |
991 | /** @var \Modules\Media\Models\Collection $defaultTemplates */ |
992 | $defaultTemplates = CollectionMapper::get() |
993 | ->with('sources') |
994 | ->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_TEMPLATES]->content) |
995 | ->execute(); |
996 | |
997 | /** @var \Modules\Media\Models\Collection $defaultAssets */ |
998 | $defaultAssets = CollectionMapper::get() |
999 | ->with('sources') |
1000 | ->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_ASSETS]->content) |
1001 | ->execute(); |
1002 | |
1003 | $view->data['defaultTemplates'] = $defaultTemplates; |
1004 | $view->data['defaultAssets'] = $defaultAssets; |
1005 | $view->data['bill'] = $bill; |
1006 | |
1007 | // @todo: add bill data such as company name bank information, ..., etc. |
1008 | |
1009 | $pdf = $view->render(); |
1010 | |
1011 | $path = $this->createBillDir($bill); |
1012 | $pdfDir = __DIR__ . '/../../../Modules/Media/Files' . $path; |
1013 | |
1014 | $status = \is_dir($pdfDir) ? true : \mkdir($pdfDir, 0755, true); |
1015 | if (!$status) { |
1016 | // @codeCoverageIgnoreStart |
1017 | $response->set($request->uri->__toString(), new FormValidation(['status' => $status])); |
1018 | $response->header->status = RequestStatusCode::R_400; |
1019 | |
1020 | return; |
1021 | // @codeCoverageIgnoreEnd |
1022 | } |
1023 | |
1024 | $billFileName = ($bill->billDate?->format('Y-m-d') ?? '0') . '_' . $bill->number . '.pdf'; |
1025 | |
1026 | \file_put_contents($pdfDir . '/' . $billFileName, $pdf); |
1027 | if (!\is_file($pdfDir . '/' . $billFileName)) { |
1028 | $response->header->status = RequestStatusCode::R_400; |
1029 | |
1030 | return; |
1031 | } |
1032 | |
1033 | $media = $this->app->moduleManager->get('Media', 'Api')->createDbEntry( |
1034 | status: [ |
1035 | 'status' => UploadStatus::OK, |
1036 | 'name' => $billFileName, |
1037 | 'path' => $pdfDir, |
1038 | 'filename' => $billFileName, |
1039 | 'size' => \filesize($pdfDir . '/' . $billFileName), |
1040 | 'extension' => 'pdf', |
1041 | ], |
1042 | account: $request->header->account, |
1043 | virtualPath: $path, |
1044 | ip: $request->getOrigin(), |
1045 | app: $this->app, |
1046 | readContent: true, |
1047 | unit: $this->app->unitId |
1048 | ); |
1049 | |
1050 | // Send bill via email |
1051 | // @todo: maybe not all bill types, and bill status (e.g. deleted should not be sent) |
1052 | $client = ClientMapper::get() |
1053 | ->with('account') |
1054 | ->with('attributes') |
1055 | ->with('attributes/type') |
1056 | ->with('attributes/value') |
1057 | ->where('id', $bill->client?->id ?? 0) |
1058 | ->where('attributes/type/name', ['bill_emails', 'bill_email_address'], 'IN') |
1059 | ->execute(); |
1060 | |
1061 | if ($client->getAttribute('bill_emails')->value->getValue() === 1) { |
1062 | $email = empty($tmp = $client->getAttribute('bill_email_address')->value->getValue()) |
1063 | ? (string) $tmp |
1064 | : $client->account->getEmail(); |
1065 | |
1066 | $this->sendBillEmail($media, $email, $response->header->l11n->language); |
1067 | } |
1068 | |
1069 | // Add type to media |
1070 | /** @var \Model\Setting $originalType */ |
1071 | $originalType = $this->app->appSettings->get( |
1072 | names: SettingsEnum::ORIGINAL_MEDIA_TYPE, |
1073 | module: self::NAME |
1074 | ); |
1075 | |
1076 | $this->createModelRelation( |
1077 | $request->header->account, |
1078 | $media->id, |
1079 | (int) $originalType->content, |
1080 | MediaMapper::class, |
1081 | 'types', |
1082 | '', |
1083 | $request->getOrigin() |
1084 | ); |
1085 | |
1086 | // Add media to bill |
1087 | $this->createModelRelation( |
1088 | $request->header->account, |
1089 | $bill->id, |
1090 | $media->id, |
1091 | BillMapper::class, |
1092 | 'files', |
1093 | '', |
1094 | $request->getOrigin() |
1095 | ); |
1096 | |
1097 | $this->createStandardCreateResponse($request, $response, $media); |
1098 | } |
1099 | |
1100 | /** |
1101 | * Send bill as email |
1102 | * |
1103 | * @param Media $media Media to send |
1104 | * @param string $email Email address |
1105 | * @param string $language Message language |
1106 | * |
1107 | * @return void |
1108 | * |
1109 | * @since 1.0.0 |
1110 | */ |
1111 | public function sendBillEmail(Media $media, string $email, string $language = 'en') : void |
1112 | { |
1113 | $handler = $this->app->moduleManager->get('Admin', 'Api')->setUpServerMailHandler(); |
1114 | |
1115 | /** @var \Model\Setting $emailFrom */ |
1116 | $emailFrom = $this->app->appSettings->get( |
1117 | names: AdminSettingsEnum::MAIL_SERVER_ADDR, |
1118 | module: 'Admin' |
1119 | ); |
1120 | |
1121 | /** @var \Model\Setting $billingTemplate */ |
1122 | $billingTemplate = $this->app->appSettings->get( |
1123 | names: SettingsEnum::BILLING_CUSTOMER_EMAIL_TEMPLATE, |
1124 | module: 'Billing' |
1125 | ); |
1126 | |
1127 | $mail = EmailMapper::get() |
1128 | ->with('l11n') |
1129 | ->where('id', (int) $billingTemplate->content) |
1130 | ->where('l11n/language', $language) |
1131 | ->execute(); |
1132 | |
1133 | $mail = new Email(); |
1134 | $mail->setFrom($emailFrom->content); |
1135 | $mail->addTo($email); |
1136 | $mail->addAttachment($media->getAbsolutePath(), $media->name); |
1137 | |
1138 | $handler->send($mail); |
1139 | |
1140 | $this->app->moduleManager->get('Billing', 'Api')->sendMail($mail); |
1141 | } |
1142 | |
1143 | /** |
1144 | * Api method to create bill files |
1145 | * |
1146 | * @param RequestAbstract $request Request |
1147 | * @param ResponseAbstract $response Response |
1148 | * @param array $data Generic data |
1149 | * |
1150 | * @return void |
1151 | * |
1152 | * @api |
1153 | * |
1154 | * @since 1.0.0 |
1155 | */ |
1156 | public function apiNoteCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1157 | { |
1158 | if (!empty($val = $this->validateNoteCreate($request))) { |
1159 | $response->header->status = RequestStatusCode::R_400; |
1160 | $this->createInvalidCreateResponse($request, $response, $val); |
1161 | |
1162 | return; |
1163 | } |
1164 | |
1165 | /** @var \Modules\Billing\Models\Bill $bill */ |
1166 | $bill = BillMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
1167 | |
1168 | $request->setData('virtualpath', $this->createBillDir($bill), true); |
1169 | $this->app->moduleManager->get('Editor')->apiEditorCreate($request, $response, $data); |
1170 | |
1171 | if ($response->header->status !== RequestStatusCode::R_200) { |
1172 | return; |
1173 | } |
1174 | |
1175 | /** @var \Modules\Editor\Models\EditorDoc $model */ |
1176 | $model = $response->getDataArray($request->uri->__toString())['response']; |
1177 | $this->createModelRelation($request->header->account, $request->getDataInt('id'), $model->id, BillMapper::class, 'bill_note', '', $request->getOrigin()); |
1178 | } |
1179 | |
1180 | /** |
1181 | * Validate bill note create request |
1182 | * |
1183 | * @param RequestAbstract $request Request |
1184 | * |
1185 | * @return array<string, bool> |
1186 | * |
1187 | * @since 1.0.0 |
1188 | */ |
1189 | private function validateNoteCreate(RequestAbstract $request) : array |
1190 | { |
1191 | $val = []; |
1192 | if (($val['id'] = !$request->hasData('id'))) { |
1193 | return $val; |
1194 | } |
1195 | |
1196 | return []; |
1197 | } |
1198 | |
1199 | /** |
1200 | * Api method to delete Bill |
1201 | * |
1202 | * @param RequestAbstract $request Request |
1203 | * @param ResponseAbstract $response Response |
1204 | * @param array $data Generic data |
1205 | * |
1206 | * @return void |
1207 | * |
1208 | * @api |
1209 | * |
1210 | * @since 1.0.0 |
1211 | */ |
1212 | public function apiBillDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1213 | { |
1214 | if (!empty($val = $this->validateBillDelete($request))) { |
1215 | $response->header->status = RequestStatusCode::R_400; |
1216 | $this->createInvalidDeleteResponse($request, $response, $val); |
1217 | |
1218 | return; |
1219 | } |
1220 | |
1221 | /** @var \Modules\Billing\Models\Bill $old */ |
1222 | $old = BillMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
1223 | |
1224 | // @todo: check if bill can be deleted |
1225 | // @todo: adjust stock transfer |
1226 | |
1227 | $new = $this->deleteBillFromRequest($request, clone $old); |
1228 | $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin()); |
1229 | $this->createStandardDeleteResponse($request, $response, $old); |
1230 | } |
1231 | |
1232 | /** |
1233 | * Method to create a bill from request. |
1234 | * |
1235 | * @param RequestAbstract $request Request |
1236 | * @param Bill $new Bill |
1237 | * |
1238 | * @return Bill |
1239 | * |
1240 | * @since 1.0.0 |
1241 | */ |
1242 | public function deleteBillFromRequest(RequestAbstract $request, Bill $new) : Bill |
1243 | { |
1244 | $new->status = BillStatus::DELETED; |
1245 | |
1246 | return $new; |
1247 | } |
1248 | |
1249 | /** |
1250 | * Validate Bill delete request |
1251 | * |
1252 | * @param RequestAbstract $request Request |
1253 | * |
1254 | * @return array<string, bool> |
1255 | * |
1256 | * @todo: implement |
1257 | * |
1258 | * @since 1.0.0 |
1259 | */ |
1260 | private function validateBillDelete(RequestAbstract $request) : array |
1261 | { |
1262 | $val = []; |
1263 | if (($val['id'] = !$request->hasData('id'))) { |
1264 | return $val; |
1265 | } |
1266 | |
1267 | return []; |
1268 | } |
1269 | |
1270 | /** |
1271 | * Api method to update BillElement |
1272 | * |
1273 | * @param RequestAbstract $request Request |
1274 | * @param ResponseAbstract $response Response |
1275 | * @param array $data Generic data |
1276 | * |
1277 | * @return void |
1278 | * |
1279 | * @api |
1280 | * |
1281 | * @since 1.0.0 |
1282 | */ |
1283 | public function apiBillElementUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1284 | { |
1285 | if (!empty($val = $this->validateBillElementUpdate($request))) { |
1286 | $response->header->status = RequestStatusCode::R_400; |
1287 | $this->createInvalidUpdateResponse($request, $response, $val); |
1288 | |
1289 | return; |
1290 | } |
1291 | |
1292 | /** @var BillElement $old */ |
1293 | $old = BillElementMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
1294 | |
1295 | // @todo: can be edited? |
1296 | // @todo: adjust transfer protocolls |
1297 | |
1298 | $new = $this->updateBillElementFromRequest($request, clone $old); |
1299 | |
1300 | $this->updateModel($request->header->account, $old, $new, BillElementMapper::class, 'bill_element', $request->getOrigin()); |
1301 | $this->createStandardUpdateResponse($request, $response, $new); |
1302 | } |
1303 | |
1304 | /** |
1305 | * Method to update BillElement from request. |
1306 | * |
1307 | * @param RequestAbstract $request Request |
1308 | * @param BillElement $new Model to modify |
1309 | * |
1310 | * @return BillElement |
1311 | * |
1312 | * @todo: implement |
1313 | * |
1314 | * @since 1.0.0 |
1315 | */ |
1316 | public function updateBillElementFromRequest(RequestAbstract $request, BillElement $new) : BillElement |
1317 | { |
1318 | return $new; |
1319 | } |
1320 | |
1321 | /** |
1322 | * Validate BillElement update request |
1323 | * |
1324 | * @param RequestAbstract $request Request |
1325 | * |
1326 | * @return array<string, bool> |
1327 | * |
1328 | * @todo: implement |
1329 | * |
1330 | * @since 1.0.0 |
1331 | */ |
1332 | private function validateBillElementUpdate(RequestAbstract $request) : array |
1333 | { |
1334 | $val = []; |
1335 | if (($val['id'] = !$request->hasData('id'))) { |
1336 | return $val; |
1337 | } |
1338 | |
1339 | return []; |
1340 | } |
1341 | |
1342 | /** |
1343 | * Api method to delete BillElement |
1344 | * |
1345 | * @param RequestAbstract $request Request |
1346 | * @param ResponseAbstract $response Response |
1347 | * @param array $data Generic data |
1348 | * |
1349 | * @return void |
1350 | * |
1351 | * @api |
1352 | * |
1353 | * @since 1.0.0 |
1354 | */ |
1355 | public function apiBillElementDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1356 | { |
1357 | if (!empty($val = $this->validateBillElementDelete($request))) { |
1358 | $response->header->status = RequestStatusCode::R_400; |
1359 | $this->createInvalidDeleteResponse($request, $response, $val); |
1360 | |
1361 | return; |
1362 | } |
1363 | |
1364 | // @todo: check if can be deleted |
1365 | // @todo: handle transactions and bill update |
1366 | |
1367 | /** @var \Modules\Billing\Models\BillElement $billElement */ |
1368 | $billElement = BillElementMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
1369 | $this->deleteModel($request->header->account, $billElement, BillElementMapper::class, 'bill_element', $request->getOrigin()); |
1370 | $this->createStandardDeleteResponse($request, $response, $billElement); |
1371 | } |
1372 | |
1373 | /** |
1374 | * Validate BillElement delete request |
1375 | * |
1376 | * @param RequestAbstract $request Request |
1377 | * |
1378 | * @return array<string, bool> |
1379 | * |
1380 | * @since 1.0.0 |
1381 | */ |
1382 | private function validateBillElementDelete(RequestAbstract $request) : array |
1383 | { |
1384 | $val = []; |
1385 | if (($val['id'] = !$request->hasData('id'))) { |
1386 | return $val; |
1387 | } |
1388 | |
1389 | return []; |
1390 | } |
1391 | |
1392 | /** |
1393 | * Api method to update Note |
1394 | * |
1395 | * @param RequestAbstract $request Request |
1396 | * @param ResponseAbstract $response Response |
1397 | * @param array $data Generic data |
1398 | * |
1399 | * @return void |
1400 | * |
1401 | * @api |
1402 | * |
1403 | * @since 1.0.0 |
1404 | */ |
1405 | public function apiNoteUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1406 | { |
1407 | // @todo: check permissions |
1408 | $this->app->moduleManager->get('Editor', 'Api')->apiEditorDocUpdate($request, $response, $data); |
1409 | } |
1410 | |
1411 | /** |
1412 | * Api method to delete Note |
1413 | * |
1414 | * @param RequestAbstract $request Request |
1415 | * @param ResponseAbstract $response Response |
1416 | * @param array $data Generic data |
1417 | * |
1418 | * @return void |
1419 | * |
1420 | * @api |
1421 | * |
1422 | * @since 1.0.0 |
1423 | */ |
1424 | public function apiNoteDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1425 | { |
1426 | // @todo: check permissions |
1427 | $this->app->moduleManager->get('Editor', 'Api')->apiEditorDocDelete($request, $response, $data); |
1428 | } |
1429 | } |