This commit is contained in:
Dennis Eichhorn 2024-03-10 02:24:56 +00:00
parent e320b48d38
commit 3cd6a79222
8 changed files with 201 additions and 32 deletions

View File

@ -355,6 +355,18 @@
"null": false,
"default": null
},
"billing_tax_tax1_account": {
"name": "billing_tax_tax1_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_tax2_account": {
"name": "billing_tax_tax2_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_refund_account": {
"name": "billing_tax_refund_account",
"type": "VARCHAR(10)",
@ -367,6 +379,24 @@
"null": false,
"default": null
},
"billing_tax_cashback_account": {
"name": "billing_tax_cashback_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_overpayment_account": {
"name": "billing_tax_overpayment_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_underpayment_account": {
"name": "billing_tax_underpayment_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_min_price": {
"name": "billing_tax_min_price",
"type": "BIGINT",

View File

@ -40,6 +40,17 @@ return [
],
],
],
'^.*/sales/bill/archive(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingSalesArchive',
'verb' => RouteVerb::GET,
'permission' => [
'module' => BackendController::NAME,
'type' => PermissionType::READ,
'state' => PermissionCategory::SALES_INVOICE,
],
],
],
'^.*/sales/bill(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingSalesInvoice',
@ -74,6 +85,17 @@ return [
],
],
],
'^.*/purchase/bill/archive(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingPurchaseArchive',
'verb' => RouteVerb::GET,
'permission' => [
'module' => BackendController::NAME,
'type' => PermissionType::READ,
'state' => PermissionCategory::PURCHASE_INVOICE,
],
],
],
'^.*/purchase/bill(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingPurchaseInvoice',
@ -119,6 +141,17 @@ return [
],
],
],
'^.*/warehouse/bill/archive(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingStockArchive',
'verb' => RouteVerb::GET,
'permission' => [
'module' => BackendController::NAME,
'type' => PermissionType::READ,
'state' => PermissionCategory::PURCHASE_INVOICE,
],
],
],
'^.*/warehouse/bill(\?.*$|$)' => [
[
'dest' => '\Modules\Billing\Controller\BackendController:viewBillingStockInvoice',

View File

@ -170,6 +170,27 @@ final class ApiBillController extends Controller
public function apiBillFinalize(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
{
if (!$this->app->accountManager->get($request->header->account)->hasPermission(
PermissionType::READ,
$this->app->unitId,
null,
self::NAME,
PermissionCategory::SALES_INVOICE
)
&& !$this->app->accountManager->get($request->header->account)->hasPermission(
PermissionType::READ,
$this->app->unitId,
null,
self::NAME,
PermissionCategory::PURCHASE_INVOICE
)
) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
// Archive bill
/** @var \Modules\Billing\Models\Bill $bill */
$old = BillMapper::get()
@ -182,22 +203,22 @@ final class ApiBillController extends Controller
$this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin());
$this->app->eventManager->triggerSimilar('PRE:Module:' . self::NAME . '-bill-finalize', '', [
$request->header->account,
null, $bill,
null, self::NAME . '-bill-finalize',
self::NAME,
(string) $bill->id,
null,
$request->getOrigin(),
]);
// Create final pdf
$this->apiBillPdfArchiveCreate($request, $response, $data);
$media = $response->getDataArray($request->uri->__toString())['response'];
$this->app->eventManager->triggerSimilar('PRE:Module:' . self::NAME . '-bill-finalize', '', [
$request->header->account,
null, $new,
null, self::NAME . '-bill-finalize',
self::NAME,
(string) $new->id,
null,
$request->getOrigin(),
]);
// Send bill via email
$this->apiBillEmail($request, $response, ['bill' => $old, 'media' => $media]);
$this->apiBillEmail($request, $response, ['bill' => $new, 'media' => $media]);
$this->createStandardUpdateResponse($request, $response, $new);
}
@ -1334,6 +1355,14 @@ final class ApiBillController extends Controller
$response->set($request->uri->__toString(), new FormValidation(['status' => $status]));
$response->header->status = RequestStatusCode::R_400;
\phpOMS\Log\FileLogger::getInstance()->error(
\phpOMS\Log\FileLogger::MSG_FULL, [
'message' => 'Couldn\'t create bill path: ' . $bill->id,
'line' => __LINE__,
'file' => self::class,
]
);
return;
// @codeCoverageIgnoreEnd
}
@ -1352,12 +1381,22 @@ final class ApiBillController extends Controller
\file_put_contents($pdfDir . '/' . $billFileName, $pdf);
if (!\is_file($pdfDir . '/' . $billFileName)) {
$response->header->status = RequestStatusCode::R_400;
$response->set($request->uri->__toString(), []);
\phpOMS\Log\FileLogger::getInstance()->error(
\phpOMS\Log\FileLogger::MSG_FULL, [
'message' => 'Couldn\'t render bill pdf: ' . $bill->id,
'line' => __LINE__,
'file' => self::class,
]
);
return;
}
$media = null;
if ($oldFile->id === 0) {
// Creating new bill archive pdf
$media = $this->app->moduleManager->get('Media', 'Api')->createDbEntry(
status: [
'status' => UploadStatus::OK,
@ -1397,12 +1436,12 @@ final class ApiBillController extends Controller
$request->getOrigin()
);
} else {
// Updating existing bill archive pdf
$media = clone $oldFile;
if (\realpath($pdfDir . '/' . $billFileName) !== \realpath($oldFile->getAbsolutePath())) {
\unlink($oldFile->getAbsolutePath());
}
$media->setPath(\Modules\Media\Controller\ApiController::normalizeDbPath($pdfDir . '/' . $billFileName));
$media->setVirtualPath($path);
$media->size = \filesize($media->getAbsolutePath());

View File

@ -70,6 +70,54 @@ final class BackendController extends Controller
->with('type')
->with('type/l11n')
->with('client')
->where('status', BillStatus::DRAFT)
->where('type/transferType', BillTransferType::SALES)
->where('type/l11n/language', $response->header->l11n->language)
->sort('id', OrderType::DESC)
->where('unit', $this->app->unitId)
->limit(25);
if ($request->getData('ptype') === 'p') {
$view->data['bills'] = $mapperQuery
->where('id', $request->getDataInt('id') ?? 0, '<')
->where('client', null, '!=')
->execute();
} elseif ($request->getData('ptype') === 'n') {
$view->data['bills'] = $mapperQuery->where('id', $request->getDataInt('id') ?? 0, '>')
->where('client', null, '!=')
->execute();
} else {
$view->data['bills'] = $mapperQuery->where('id', 0, '>')
->where('client', null, '!=')
->execute();
}
return $view;
}
/**
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return RenderableInterface
*
* @since 1.0.0
* @codeCoverageIgnore
*/
public function viewBillingSalesArchive(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
{
$view = new View($this->app->l11nManager, $request, $response);
$view->setTemplate('/Modules/Billing/Theme/Backend/sales-bill-list');
$view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1005104001, $request, $response);
$mapperQuery = SalesBillMapper::getAll()
->with('type')
->with('type/l11n')
->with('client')
->where('status', BillStatus::DRAFT, '!=')
->where('type/transferType', BillTransferType::SALES)
->where('type/l11n/language', $response->header->l11n->language)
->sort('id', OrderType::DESC)

View File

@ -49,10 +49,24 @@ class TaxCombination implements \JsonSerializable
public string $account = '';
// Tax accounts can be defined in:
// 1. Account (gross postings are automatically split)
// 2. Tax code
// 3. Tax combination
public string $taxAccount1 = '';
public string $taxAccount2 = '';
public string $refundAccount = '';
public string $discountAccount = '';
public string $cashbackAccount = '';
public string $overpaymentAccount = '';
public string $underpaymentAccount = '';
public ?int $minPrice = null;
public ?int $maxPrice = null;

View File

@ -47,8 +47,13 @@ final class TaxCombinationMapper extends DataMapperFactory
'billing_tax_code' => ['name' => 'billing_tax_code', 'type' => 'string', 'internal' => 'taxCode'],
'billing_tax_type' => ['name' => 'billing_tax_type', 'type' => 'int', 'internal' => 'taxType'],
'billing_tax_account' => ['name' => 'billing_tax_account', 'type' => 'string', 'internal' => 'account'],
'billing_tax_tax1_account' => ['name' => 'billing_tax_tax1_account', 'type' => 'string', 'internal' => 'taxAccount1'],
'billing_tax_tax2_account' => ['name' => 'billing_tax_tax2_account', 'type' => 'string', 'internal' => 'taxAccount2'],
'billing_tax_refund_account' => ['name' => 'billing_tax_refund_account', 'type' => 'string', 'internal' => 'refundAccount'],
'billing_tax_discount_account' => ['name' => 'billing_tax_discount_account', 'type' => 'string', 'internal' => 'discountAccount'],
'billing_tax_cashback_account' => ['name' => 'billing_tax_cashback_account', 'type' => 'string', 'internal' => 'cashbackAccount'],
'billing_tax_overpayment_account' => ['name' => 'billing_tax_overpayment_account', 'type' => 'string', 'internal' => 'overpaymentAccount'],
'billing_tax_underpayment_account' => ['name' => 'billing_tax_underpayment_account', 'type' => 'string', 'internal' => 'underpaymentAccount'],
'billing_tax_min_price' => ['name' => 'billing_tax_min_price', 'type' => 'int', 'internal' => 'minPrice'],
'billing_tax_max_price' => ['name' => 'billing_tax_max_price', 'type' => 'int', 'internal' => 'maxPrice'],
'billing_tax_start' => ['name' => 'billing_tax_start', 'type' => 'DateTime', 'internal' => 'start'],

View File

@ -333,11 +333,11 @@ echo $this->data['nav']->render(); ?>
</button><input name="item_number" type="text" autocomplete="off"></span>
<td><textarea name="item_description" autocomplete="off"></textarea>
<td><input name="item_quantity" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountr" type="number" min="-100" max="100" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_price" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="any" value="" autocomplete="off">
<td><input name="item_price" type="number" step="any" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="any" value="" autocomplete="off">
<td>
<td>
</tr>
@ -356,11 +356,11 @@ echo $this->data['nav']->render(); ?>
</button><input name="item_number" autocomplete="off" type="text" value="<?= $element->itemNumber; ?>"<?= $disabled; ?>></span>
<td><textarea name="item_description" autocomplete="off"<?= $disabled; ?>><?= $element->itemName; ?></textarea>
<td><input name="item_quantity" autocomplete="off" type="number" step="any" value="<?= $element->quantity->sub($element->discountQ)->getAmount($element->container->quantityDecimals); ?>"<?= $disabled; ?>>
<td><input name="item_discountp" autocomplete="off" type="number" step="0.01" value="<?= $element->singleDiscountP->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountr" autocomplete="off" type="number" step="0.01" value="<?= $element->singleDiscountR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountp" autocomplete="off" type="number" step="any" value="<?= $element->singleDiscountP->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountr" autocomplete="off" type="number" step="any" value="<?= $element->singleDiscountR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_bonus" autocomplete="off" type="number" min="-100" max="100" step="0.01" value="<?= $element->discountQ->getAmount($element->container->quantityDecimals); ?>"<?= $disabled; ?>>
<td><input name="item_price" autocomplete="off" type="number" step="0.01" value="<?= $element->singleSalesPriceNet->getFloat(); ?>"<?= $disabled; ?>>
<td><input name="item_taxr" autocomplete="off" type="number" step="0.01" value="<?= $element->taxR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_price" autocomplete="off" type="number" step="any" value="<?= $element->singleSalesPriceNet->getFloat(); ?>"<?= $disabled; ?>>
<td><input name="item_taxr" autocomplete="off" type="number" step="any" value="<?= $element->taxR->getAmount(); ?>"<?= $disabled; ?>>
<td><?= $this->getCurrency($element->totalSalesPriceNet, symbol: ''); ?>
<td><?= \number_format($element->totalSalesPriceNet->value === 0 ? 0 : (1 - $element->totalPurchasePriceNet->value / $element->totalSalesPriceNet->value) * 100, 2); ?>%
<?php endforeach; ?>

View File

@ -331,11 +331,11 @@ echo $this->data['nav']->render(); ?>
</button><input name="item_number" type="text" autocomplete="off"></span>
<td><textarea name="item_description" autocomplete="off"></textarea>
<td><input name="item_quantity" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountr" type="number" min="-100" max="100" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_price" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="any" value="" autocomplete="off">
<td><input name="item_price" type="number" step="any" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="any" value="" autocomplete="off">
<td>
<td>
</tr>
@ -354,11 +354,11 @@ echo $this->data['nav']->render(); ?>
</button><input name="item_number" autocomplete="off" type="text" value="<?= $element->itemNumber; ?>"<?= $disabled; ?>></span>
<td><textarea name="item_description" autocomplete="off"<?= $disabled; ?>><?= $element->itemName; ?></textarea>
<td><input name="item_quantity" autocomplete="off" type="number" step="any" value="<?= $element->quantity->sub($element->discountQ)->getAmount($element->container->quantityDecimals); ?>"<?= $disabled; ?>>
<td><input name="item_discountp" autocomplete="off" type="number" step="0.01" value="<?= $element->singleDiscountP->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountr" autocomplete="off" type="number" step="0.01" value="<?= $element->singleDiscountR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountp" autocomplete="off" type="number" step="any" value="<?= $element->singleDiscountP->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_discountr" autocomplete="off" type="number" step="any" value="<?= $element->singleDiscountR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_bonus" autocomplete="off" type="number" min="-100" max="100" step="0.01" value="<?= $element->discountQ->getAmount($element->container->quantityDecimals); ?>"<?= $disabled; ?>>
<td><input name="item_price" autocomplete="off" type="number" step="0.01" value="<?= $element->singleSalesPriceNet->getFloat(); ?>"<?= $disabled; ?>>
<td><input name="item_taxr" autocomplete="off" type="number" step="0.01" value="<?= $element->taxR->getAmount(); ?>"<?= $disabled; ?>>
<td><input name="item_price" autocomplete="off" type="number" step="any" value="<?= $element->singleSalesPriceNet->getFloat(); ?>"<?= $disabled; ?>>
<td><input name="item_taxr" autocomplete="off" type="number" step="any" value="<?= $element->taxR->getAmount(); ?>"<?= $disabled; ?>>
<td><?= $this->getCurrency($element->totalSalesPriceNet, symbol: ''); ?>
<td><?= \number_format($element->totalSalesPriceNet->value === 0 ? 0 : (1 - $element->totalPurchasePriceNet->value / $element->totalSalesPriceNet->value) * 100, 2); ?>%
<?php endforeach; ?>
@ -372,11 +372,11 @@ echo $this->data['nav']->render(); ?>
<i class="g-icon">book</i></button><input name="item_number" type="text" autocomplete="off"></span>
<td><textarea name="item_description" autocomplete="off"></textarea>
<td><input name="item_quantity" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_discountp" type="number" step="any" value="" autocomplete="off">
<td><input name="item_discountr" type="number" min="-100" max="100" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_price" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="0.01" value="" autocomplete="off">
<td><input name="item_bonus" type="number" step="any" value="" autocomplete="off">
<td><input name="item_price" type="number" step="any" value="" autocomplete="off">
<td><input name="item_taxr" type="number" step="any" value="" autocomplete="off">
<td>
<td>
<?php endif; ?>