Merge branch 'main' of http://192.168.1.200:3000/Stratawiz/Rapport-Laravel
Specific Folder FTP Upload / ftp-deploy (push) Successful in 1m45s Details

This commit is contained in:
zeeshan 2026-10-16 12:16:59 +05:30
commit 0ed9417ace
14 changed files with 1441 additions and 175 deletions

View File

@ -0,0 +1,756 @@
<?php
namespace App\Exports;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\StreamedResponse;
class AdherencePobDriveExport
{
private const GREEN = 'C6EFCE';
private const GREY = 'D9D9D9';
private const PINK = 'F8CBAD';
/** @var array<int, mixed> */
private $abmRows;
/** @var array<int, mixed> */
private $rbmRows;
/** @var array<int, mixed> */
private $tmRows;
/** @var array<int, array<string, mixed>> */
private $dayTemplate;
private $month;
public function __construct($data, string $month)
{
$this->abmRows = $this->listRows($data, 'abmReport');
$this->rbmRows = $this->listRows($data, 'rbmReport');
$this->tmRows = $this->listRows($data, 'tmReport');
$this->dayTemplate = $this->dayTemplate();
$this->month = $month;
}
public function download(): StreamedResponse
{
$spreadsheet = $this->spreadsheet();
$filename = $this->filename();
return response()->streamDownload(function () use ($spreadsheet) {
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
$spreadsheet->disconnectWorksheets();
}, $filename, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
]);
}
/**
* @return array<int, array<string, mixed>>
*/
public function sections(): array
{
return [
$this->abmSection(),
$this->regionSection(),
$this->territorySection(),
];
}
public function filename(): string
{
$slug = trim((string) preg_replace('/[^A-Za-z0-9]+/', '-', $this->month), '-');
return 'Adherence-Pack-POB-Drive-' . ($slug !== '' ? $slug : 'report') . '.xlsx';
}
public function spreadsheet(): Spreadsheet
{
$spreadsheet = new Spreadsheet();
$abm = $spreadsheet->getActiveSheet();
$abm->setTitle('Details ABM wise');
$this->writeAbmSheet($abm);
$region = $spreadsheet->createSheet();
$region->setTitle('Region Daily Winners');
$this->writeRegionSheet($region);
$territory = $spreadsheet->createSheet();
$territory->setTitle('Territory Daily Winners');
$this->writeTerritorySheet($territory);
$spreadsheet->setActiveSheetIndex(0);
return $spreadsheet;
}
private function writeAbmSheet($sheet): void
{
$identity = [
'ZBM',
'RBM',
'ABM HQ',
'HQ Div',
'ABM Name',
'Emp ID',
'Reporting Manager (RBM)',
'Email',
'Approved Count per ABM',
'Remarks',
];
$summary = [
'Working Days',
'Daily Target (POBs = Approved Count)',
'Month Target (POBs)',
'Total TMs Qualified (≥₹15k POB)',
'Achievement %',
'Total Chemists Qualified (≥₹15k POB)',
'Days Daily Target Met',
'Target Status',
];
$summaryHelp = [
'No. of Total W',
'Count of Active TMs',
'No. of Total W x Count of Active TMs',
'Sum of count of all Days',
'Total TMs Qualified / Month Target (POBs)',
'No. of Unique Chemists with ≥₹15k POB Value',
'No. of Days on which Daily Target is achieved',
'If Month Target achieved = Achieved, If Month Target not achieved = In Progress',
];
$dayStart = count($identity) + 1;
$summaryStart = $dayStart + count($this->dayTemplate);
$lastCol = $summaryStart + count($summary) - 1;
$sheet->setCellValue('A1', 'Adherence Pack POB Drive – ABM – ' . $this->month);
$sheet->mergeCells('A1:' . $this->col($lastCol) . '1');
$sheet->setCellValue('A3', 'Legend: pink = vacant HQ; green day = working day; grey day = holiday.');
$sheet->mergeCells('A3:' . $this->col($lastCol) . '3');
$sheet->setCellValue('A5', 'Data of no. of chemists with Adherence Pack POB ≥ ₹15,000');
$sheet->setCellValue('J5', 'Day type (W / H) →');
foreach ($this->dayTemplate as $index => $day) {
$sheet->setCellValue($this->col($dayStart + $index) . '5', $day['type'] === 'H' ? 'H' : 'W');
}
$help = [
'Zone',
'Region',
'Area',
'as it is',
'ABM Emp Name',
'ABM Emp Code',
'RBM Name',
'ABM Email ID',
'Count of Active TMs',
'Blank Remarks Column',
];
$this->writeValues($sheet, 6, $help);
if (count($this->dayTemplate) > 0) {
$sheet->setCellValue($this->col($dayStart) . '6', 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value');
if (count($this->dayTemplate) > 1) {
$sheet->mergeCells($this->col($dayStart) . '6:' . $this->col($summaryStart - 1) . '6');
}
}
$this->writeValues($sheet, 6, $summaryHelp, $summaryStart);
$headers = $identity;
foreach ($this->dayTemplate as $day) {
$headers[] = $this->dayHeader($day['label']);
}
$headers = array_merge($headers, $summary);
$this->writeValues($sheet, 7, $headers);
$this->styleHeaderRow($sheet, 7, $lastCol);
$this->paintDayTypes($sheet, 5, $dayStart, false);
$this->paintDayHeaders($sheet, 7, $dayStart, false);
$rowNumber = 8;
foreach ($this->abmRows as $row) {
$values = [];
foreach ($identity as $key) {
$values[] = $this->field($row, $key);
}
$dayMap = $this->dayMap($this->field($row, 'days'));
foreach ($this->dayTemplate as $day) {
$values[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value');
}
foreach ($summary as $key) {
$values[] = $this->field($row, $key);
}
$this->writeValues($sheet, $rowNumber, $values);
$this->paintDataDays($sheet, $rowNumber, $dayStart, false);
if ($this->isVacantName($this->field($row, 'ABM HQ')) || $this->isVacantName($this->field($row, 'ABM Name'))) {
$this->paintRow($sheet, $rowNumber, $lastCol, self::PINK);
}
$rowNumber++;
}
$this->finishSheet($sheet, 7, max(7, $rowNumber - 1), $lastCol, $dayStart);
$sheet->freezePane('A8');
}
private function writeRegionSheet($sheet): void
{
$columns = $this->regionColumns();
$lastCol = 3 + count($columns);
$sheet->setCellValue('A1', 'Region-wise Daily Winner – Highest no. of chemists with Adherence Pack POB ≥ ₹15,000 – ' . $this->month);
$sheet->mergeCells('A1:' . $this->col($lastCol) . '1');
$typeRow = ['', '', ''];
$helpRow = ['', '', ''];
$headerRow = ['Region (RBM)', 'RBM Name', 'RBM Employee Code'];
$pairHelp = 0;
foreach ($columns as $column) {
if ($column['kind'] === 'total-tm' || $column['kind'] === 'total-chemist') {
$typeRow[] = 'Sum of count of all Days';
$helpRow[] = $column['kind'] === 'total-tm'
? 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value'
: 'No. of Unique Chemists with ≥₹15k POB Value';
$headerRow[] = $column['header'];
continue;
}
$typeRow[] = $column['type'] === 'H' ? 'H' : 'W';
if ($pairHelp === 0 && $column['kind'] === 'tm') {
$helpRow[] = 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value';
$pairHelp = 1;
} elseif ($pairHelp === 1 && $column['kind'] === 'chemist') {
$helpRow[] = 'No. of Unique Chemists with ≥₹15k POB Value';
$pairHelp = 2;
} else {
$helpRow[] = '';
}
$headerRow[] = $column['header'];
}
$this->writeValues($sheet, 2, $typeRow);
$this->writeValues($sheet, 3, $helpRow);
$this->writeValues($sheet, 4, $headerRow);
if (count($columns) >= 2 && $columns[count($columns) - 1]['kind'] === 'total-chemist') {
$sheet->mergeCells($this->col($lastCol - 1) . '2:' . $this->col($lastCol) . '2');
}
$this->styleHeaderRow($sheet, 4, $lastCol);
$this->paintRegionDayMarks($sheet, $columns);
$rowNumber = 5;
foreach ($this->rbmRows as $row) {
$dayMap = $this->dayMap($this->field($row, 'days'));
$values = [
$this->field($row, 'Region (RBM)'),
$this->field($row, 'RBM Name'),
$this->field($row, 'RBM Employee Code'),
];
foreach ($columns as $column) {
if ($column['kind'] === 'total-tm') {
$values[] = $this->field($row, 'Total TMs Qualified (≥₹15k POB)');
continue;
}
if ($column['kind'] === 'total-chemist') {
$values[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)');
continue;
}
$day = $dayMap[$column['date']] ?? $column['day'];
$values[] = $column['kind'] === 'holiday'
? 'Holiday'
: $this->dayValue($day, $column['kind'] === 'chemist' ? 'chemistsQualified' : 'value');
}
$this->writeValues($sheet, $rowNumber, $values);
$this->paintRegionDataDays($sheet, $rowNumber, $columns);
if ($this->isVacantName($values[0]) || $this->isVacantName($values[1])) {
$this->paintRow($sheet, $rowNumber, $lastCol, self::PINK);
}
$rowNumber++;
}
$this->finishSheet($sheet, 4, max(4, $rowNumber - 1), $lastCol, 4);
$sheet->freezePane('A5');
}
private function writeTerritorySheet($sheet): void
{
$dayStart = 4;
$totalCol = $dayStart + count($this->dayTemplate);
$sheet->setCellValue('A1', 'Territory-wise Daily Winner – Highest no. of chemists with Adherence Pack POB ≥ ₹15,000 – ' . $this->month);
$sheet->mergeCells('A1:' . $this->col($totalCol) . '1');
$typeRow = ['', '', ''];
foreach ($this->dayTemplate as $day) {
$typeRow[] = $day['type'] === 'H' ? 'H' : 'W';
}
$this->writeValues($sheet, 3, $typeRow);
if (count($this->dayTemplate) > 0) {
$sheet->setCellValue('D4', 'No. of Unique Chemists with ≥₹15k POB Value');
}
$sheet->setCellValue($this->col($totalCol) . '4', 'Sum of all Days of No. of Unique Chemists with ≥₹15k POB Value');
$headers = ['Territory', 'TM Name', 'TM Employee Code'];
foreach ($this->dayTemplate as $day) {
$headers[] = $this->dayHeader($day['label']);
}
$headers[] = 'Total Chemists Qualified (≥₹15k POB)';
$this->writeValues($sheet, 5, $headers);
$this->styleHeaderRow($sheet, 5, $totalCol);
$this->paintDayTypes($sheet, 3, $dayStart, true);
$this->paintDayHeaders($sheet, 5, $dayStart, true);
$rowNumber = 6;
foreach ($this->tmRows as $row) {
$dayMap = $this->dayMap($this->field($row, 'days'));
$values = [
$this->field($row, 'Territory'),
$this->field($row, 'TM Name'),
$this->field($row, 'TM Employee Code'),
];
foreach ($this->dayTemplate as $day) {
$values[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value');
}
$values[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)');
$this->writeValues($sheet, $rowNumber, $values);
$this->paintDataDays($sheet, $rowNumber, $dayStart, true);
if ($this->isVacantName($values[0]) || $this->isVacantName($values[1])) {
$this->paintRow($sheet, $rowNumber, $totalCol, self::PINK);
}
$rowNumber++;
}
$this->finishSheet($sheet, 5, max(5, $rowNumber - 1), $totalCol, $dayStart);
$sheet->freezePane('A6');
}
/**
* @return array<string, mixed>
*/
private function abmSection(): array
{
$identity = [
'ZBM',
'RBM',
'ABM HQ',
'HQ Div',
'ABM Name',
'Emp ID',
'Reporting Manager (RBM)',
'Email',
'Approved Count per ABM',
'Remarks',
];
$summary = [
'Working Days',
'Daily Target (POBs = Approved Count)',
'Month Target (POBs)',
'Total TMs Qualified (≥₹15k POB)',
'Achievement %',
'Total Chemists Qualified (≥₹15k POB)',
'Days Daily Target Met',
'Target Status',
];
$dayTypes = array_fill(0, count($identity), '');
$headers = $identity;
foreach ($this->dayTemplate as $day) {
$dayTypes[] = $day['type'] === 'H' ? 'H' : 'W';
$headers[] = $this->dayHeader($day['label']);
}
$dayTypes = array_merge($dayTypes, array_fill(0, count($summary), ''));
$headers = array_merge($headers, $summary);
$rows = [];
foreach ($this->abmRows as $row) {
$cells = [];
foreach ($identity as $key) {
$cells[] = $this->field($row, $key);
}
$dayMap = $this->dayMap($this->field($row, 'days'));
foreach ($this->dayTemplate as $day) {
$cells[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value');
}
foreach ($summary as $key) {
$cells[] = $this->field($row, $key);
}
$rows[] = [
'cells' => $cells,
'vacant' => $this->isVacantName($this->field($row, 'ABM HQ')) || $this->isVacantName($this->field($row, 'ABM Name')),
];
}
return [
'id' => 'abm',
'title' => 'Details ABM wise – ' . $this->month,
'dayTypes' => $dayTypes,
'headers' => $headers,
'rows' => $rows,
];
}
/**
* @return array<string, mixed>
*/
private function regionSection(): array
{
$columns = $this->regionColumns();
$dayTypes = ['', '', ''];
$headers = ['Region (RBM)', 'RBM Name', 'RBM Employee Code'];
foreach ($columns as $column) {
$dayTypes[] = $column['type'] === 'H' ? 'H' : ($column['type'] === 'W' ? 'W' : '');
$headers[] = $column['header'];
}
$rows = [];
foreach ($this->rbmRows as $row) {
$dayMap = $this->dayMap($this->field($row, 'days'));
$cells = [
$this->field($row, 'Region (RBM)'),
$this->field($row, 'RBM Name'),
$this->field($row, 'RBM Employee Code'),
];
foreach ($columns as $column) {
if ($column['kind'] === 'total-tm') {
$cells[] = $this->field($row, 'Total TMs Qualified (≥₹15k POB)');
continue;
}
if ($column['kind'] === 'total-chemist') {
$cells[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)');
continue;
}
$day = $dayMap[$column['date']] ?? $column['day'];
$cells[] = $column['kind'] === 'holiday'
? 'Holiday'
: $this->dayValue($day, $column['kind'] === 'chemist' ? 'chemistsQualified' : 'value');
}
$rows[] = [
'cells' => $cells,
'vacant' => $this->isVacantName($cells[0]) || $this->isVacantName($cells[1]),
];
}
return [
'id' => 'region',
'title' => 'Region Daily Winners – ' . $this->month,
'dayTypes' => $dayTypes,
'headers' => $headers,
'rows' => $rows,
];
}
/**
* @return array<string, mixed>
*/
private function territorySection(): array
{
$dayTypes = ['', '', ''];
$headers = ['Territory', 'TM Name', 'TM Employee Code'];
foreach ($this->dayTemplate as $day) {
$dayTypes[] = $day['type'] === 'H' ? 'H' : 'W';
$headers[] = $this->dayHeader($day['label']);
}
$dayTypes[] = '';
$headers[] = 'Total Chemists Qualified (≥₹15k POB)';
$rows = [];
foreach ($this->tmRows as $row) {
$dayMap = $this->dayMap($this->field($row, 'days'));
$cells = [
$this->field($row, 'Territory'),
$this->field($row, 'TM Name'),
$this->field($row, 'TM Employee Code'),
];
foreach ($this->dayTemplate as $day) {
$cells[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value');
}
$cells[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)');
$rows[] = [
'cells' => $cells,
'vacant' => $this->isVacantName($cells[0]) || $this->isVacantName($cells[1]),
];
}
return [
'id' => 'territory',
'title' => 'Territory Daily Winners – ' . $this->month,
'dayTypes' => $dayTypes,
'headers' => $headers,
'rows' => $rows,
];
}
/**
* @return array<int, array<string, mixed>>
*/
private function regionColumns(): array
{
$columns = [];
foreach ($this->dayTemplate as $day) {
$header = $this->dayHeader($day['label']);
if ($day['type'] === 'H') {
$columns[] = [
'kind' => 'holiday',
'type' => 'H',
'date' => $day['date'],
'header' => $header,
'day' => $day,
];
continue;
}
$columns[] = [
'kind' => 'tm',
'type' => 'W',
'date' => $day['date'],
'header' => $header,
'day' => $day,
];
$columns[] = [
'kind' => 'chemist',
'type' => 'W',
'date' => $day['date'],
'header' => $header,
'day' => $day,
];
}
$columns[] = ['kind' => 'total-tm', 'type' => '', 'header' => 'Total TMs Qualified (≥₹15k POB)'];
$columns[] = ['kind' => 'total-chemist', 'type' => '', 'header' => 'Total Chemists Qualified (≥₹15k POB)'];
return $columns;
}
/**
* @return array<int, array<string, mixed>>
*/
private function dayTemplate(): array
{
foreach ([$this->abmRows, $this->rbmRows, $this->tmRows] as $rows) {
if ($rows === []) {
continue;
}
$days = $this->orderedDays($this->field($rows[0], 'days'));
if ($days !== []) {
return $days;
}
}
return [];
}
/**
* @param mixed $days
* @return array<int, array<string, mixed>>
*/
private function orderedDays($days): array
{
if (is_object($days)) {
$days = (array) $days;
}
if (!is_array($days)) {
return [];
}
$list = [];
foreach ($days as $label => $day) {
if (is_object($day)) {
$day = (array) $day;
}
if (!is_array($day)) {
continue;
}
$list[] = [
'label' => (string) $label,
'date' => (string) ($day['date'] ?? ''),
'type' => (string) ($day['type'] ?? 'W'),
'value' => $day['value'] ?? 0,
'chemistsQualified' => $day['chemistsQualified'] ?? 0,
];
}
usort($list, function ($a, $b) {
return strcmp($a['date'], $b['date']);
});
return $list;
}
/**
* @param mixed $days
* @return array<string, array<string, mixed>>
*/
private function dayMap($days): array
{
$map = [];
foreach ($this->orderedDays($days) as $day) {
if ($day['date'] !== '') {
$map[$day['date']] = $day;
}
}
return $map;
}
/**
* @param array<string, mixed> $day
* @return mixed
*/
private function dayValue(array $day, string $field)
{
if (($day['type'] ?? '') === 'H' || ($day[$field] ?? null) === 'Holiday') {
return 'Holiday';
}
$value = $day[$field] ?? 0;
return is_numeric($value) ? 0 + $value : $value;
}
private function dayHeader(string $label): string
{
$parts = preg_split('/\s+/', trim($label), 2);
if (is_array($parts) && count($parts) === 2) {
return $parts[0] . "\n" . $parts[1];
}
return $label;
}
/**
* @param mixed $data
* @return array<int, mixed>
*/
private function listRows($data, string $key): array
{
$rows = $this->field($data, $key);
if (is_object($rows)) {
$rows = (array) $rows;
}
return is_array($rows) ? array_values($rows) : [];
}
/**
* @param mixed $row
* @return mixed
*/
private function field($row, string $key)
{
if (is_array($row)) {
return $row[$key] ?? '';
}
if (is_object($row) && isset($row->{$key})) {
return $row->{$key};
}
return '';
}
private function isVacantName($name): bool
{
return stripos(trim((string) $name), 'vacant') !== false;
}
/**
* @param array<int, mixed> $values
*/
private function writeValues($sheet, int $row, array $values, int $startColumn = 1): void
{
foreach ($values as $offset => $value) {
$sheet->setCellValue($this->col($startColumn + $offset) . $row, $value);
}
}
private function paintDayTypes($sheet, int $row, int $startColumn, bool $singleColumn): void
{
foreach ($this->dayTemplate as $index => $day) {
$this->paintCell($sheet, $this->col($startColumn + $index) . $row, $day['type'] === 'H' ? self::GREY : self::GREEN);
}
if (!$singleColumn) {
return;
}
}
private function paintDayHeaders($sheet, int $row, int $startColumn, bool $singleColumn): void
{
foreach ($this->dayTemplate as $index => $day) {
$this->paintCell($sheet, $this->col($startColumn + $index) . $row, $day['type'] === 'H' ? self::GREY : self::GREEN);
}
if ($singleColumn) {
return;
}
}
private function paintDataDays($sheet, int $row, int $startColumn, bool $singleColumn): void
{
foreach ($this->dayTemplate as $index => $day) {
if ($day['type'] === 'H') {
$this->paintCell($sheet, $this->col($startColumn + $index) . $row, self::GREY);
}
}
if ($singleColumn) {
return;
}
}
/**
* @param array<int, array<string, mixed>> $columns
*/
private function paintRegionDayMarks($sheet, array $columns): void
{
foreach ($columns as $index => $column) {
if ($column['kind'] === 'total-tm' || $column['kind'] === 'total-chemist') {
continue;
}
$cell = $this->col(4 + $index);
$color = $column['type'] === 'H' ? self::GREY : self::GREEN;
$this->paintCell($sheet, $cell . '2', $color);
$this->paintCell($sheet, $cell . '4', $color);
}
}
/**
* @param array<int, array<string, mixed>> $columns
*/
private function paintRegionDataDays($sheet, int $row, array $columns): void
{
foreach ($columns as $index => $column) {
if (($column['type'] ?? '') === 'H') {
$this->paintCell($sheet, $this->col(4 + $index) . $row, self::GREY);
}
}
}
private function paintRow($sheet, int $row, int $lastColumn, string $color): void
{
$sheet->getStyle('A' . $row . ':' . $this->col($lastColumn) . $row)->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB($color);
}
private function paintCell($sheet, string $cell, string $color): void
{
$sheet->getStyle($cell)->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB($color);
}
private function styleHeaderRow($sheet, int $row, int $lastColumn): void
{
$range = 'A' . $row . ':' . $this->col($lastColumn) . $row;
$sheet->getStyle($range)->getFont()->setBold(true);
$sheet->getStyle($range)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_CENTER)->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getRowDimension($row)->setRowHeight(32);
}
private function finishSheet($sheet, int $headerRow, int $lastRow, int $lastColumn, int $dayStart): void
{
$range = 'A1:' . $this->col($lastColumn) . $lastRow;
$sheet->getStyle($range)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
$sheet->getStyle('A' . $headerRow . ':' . $this->col($lastColumn) . $lastRow)->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN);
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
for ($column = 1; $column <= $lastColumn; $column++) {
$width = $column < $dayStart ? 22 : 14;
$sheet->getColumnDimension($this->col($column))->setWidth($width);
}
}
private function col(int $index): string
{
return Coordinate::stringFromColumnIndex($index);
}
}

View File

@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers;
use App\Exports\AdherencePobDriveExport;
use App\Http\Requests\LambdaRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AdherencepobdrivereportController extends Controller
{
private const REPORT_START = '2026-04-01';
public function __construct()
{
$this->_service = 'reports';
$this->_module = 'team2_reports';
$this->_viewname = 'adherencepobdrivereport';
$this->_title = 'Adherence Pack POB Drive';
$this->_base_url = '/adherencepobdrivereport';
}
public static function canDownload($user): bool
{
if (!$user || ($user->domain ?? '') !== 'team2') {
return false;
}
$permissions = json_decode($user->permissions ?? '[]');
if (is_array($permissions) && in_array('all_data_access', $permissions, true)) {
return true;
}
$designation = strtoupper(trim(self::tokenDesignation($user->access_token ?? '')));
return $designation === 'HO' || $designation === 'ADMIN';
}
public function list(Request $request)
{
return redirect($this->_base_url . '/report');
}
public function report(Request $request)
{
$this->assertCanDownload();
$months = $this->monthChoices();
$selected = trim((string) $request->query('month', ''));
if (!in_array($selected, $months, true)) {
$selected = $this->currentMonth();
}
$reportError = session('reportError');
$sections = [];
try {
$data = $this->fetchReport($selected);
$export = new AdherencePobDriveExport($data, $selected);
if (!method_exists($export, 'sections')) {
throw new \RuntimeException('The report view is out of date. Deploy app/Exports/AdherencePobDriveExport.php and reload PHP.');
}
$sections = $export->sections();
} catch (\Throwable $e) {
$reportError = $e->getMessage();
}
return $this->view('report')->with([
'title' => $this->_title,
'base_url' => $this->_base_url,
'months' => $months,
'selectedMonth' => $selected,
'reportError' => $reportError,
'sections' => $sections,
]);
}
public function export(Request $request)
{
$this->assertCanDownload();
$months = $this->monthChoices();
$month = trim((string) $request->query('month', ''));
if (!in_array($month, $months, true)) {
$month = $this->currentMonth();
}
try {
$data = $this->fetchReport($month);
} catch (\RuntimeException $e) {
return $this->backToReport($month, $e->getMessage());
}
return (new AdherencePobDriveExport($data, $month))->download();
}
/**
* @return object
*/
private function fetchReport(string $month)
{
$token = Auth::user()->access_token ?? '';
try {
$response = LambdaRequest::getrequest(
$this->_service,
$this->_module . '/adherence_report',
['month' => $month],
$token,
120
);
} catch (\Throwable $e) {
throw new \RuntimeException('Failed to load the adherence report.');
}
if (!is_object($response) || strtolower((string) ($response->status ?? '')) !== 'success' || !is_object($response->data ?? null)) {
$message = is_object($response) ? trim((string) ($response->message ?? '')) : '';
throw new \RuntimeException($message !== '' ? $message : 'Failed to load the adherence report.');
}
return $response->data;
}
/**
* @return array<int, string>
*/
private function monthChoices(): array
{
$cursor = new \DateTimeImmutable(self::REPORT_START);
$end = new \DateTimeImmutable('first day of this month');
$months = [];
while ($cursor <= $end) {
$months[] = $cursor->format('F Y');
$cursor = $cursor->modify('+1 month');
}
if ($months === []) {
$months[] = $this->currentMonth();
}
return $months;
}
private function currentMonth(): string
{
return date('F Y');
}
private function assertCanDownload(): void
{
if (!self::canDownload(Auth::user())) {
abort(403, 'You do not have access to this report.');
}
}
private function backToReport(string $month, string $message)
{
return redirect($this->_base_url . '/report?month=' . urlencode($month))->with('reportError', $message);
}
private static function tokenDesignation(string $token): string
{
$token = trim($token);
if (stripos($token, 'Bearer ') === 0) {
$token = trim(substr($token, 7));
}
$parts = explode('.', $token);
if (count($parts) < 2) {
return '';
}
$segment = strtr($parts[1], '-_', '+/');
$pad = strlen($segment) % 4;
if ($pad > 0) {
$segment .= str_repeat('=', 4 - $pad);
}
$payload = json_decode(base64_decode($segment));
$user = is_object($payload) ? ($payload->user ?? null) : null;
if (!is_object($user)) {
return '';
}
return (string) ($user->designation ?? '');
}
}

View File

@ -123,16 +123,9 @@ class FormController extends Controller
}
$supplyYes = ($post['supply_status'] ?? '') === 'Yes';
if (!$supplyYes) {
if (!$isExisting) {
$post['submission_status'] = 'Order Taken';
} else {
unset($post['submission_status']);
}
return $post;
}
if (!$hasInvoice) {
return ['__error' => 'Upload an invoice as a JPG, JPEG, PNG, or PDF file.'];
}
$actualUnits = 0;
$actualValue = 0;
foreach ($prices as $code => $price) {
@ -148,7 +141,7 @@ class FormController extends Controller
$post['total_actual_pob_units'] = (string) $actualUnits;
$post['total_actual_pob_value'] = (string) $actualValue;
$post['supply_status'] = 'Yes';
$post['submission_status'] = 'Invoice Uploaded';
$post['submission_status'] = $hasInvoice ? 'Invoice Uploaded' : 'Submitted';
return $post;
}
@ -5879,6 +5872,18 @@ class FormController extends Controller
}
try {
$post = $this->processformdata($formdata->form_id, $post, $formdata->user_id, $formdata->partition);
if (!empty($post['__error'])) {
$message = $post['__error'];
unset($post['__error']);
if ($inceptAllocation && $inceptUsageId) {
$inceptAllocation->release($inceptUsageId);
}
return response()->json(['status' => 'failure', 'message' => $message]);
}
$replaceSubmissionId = $post['__replace_submission_id'] ?? '';
$deleteReason = $post['__delete_reason'] ?? '';
$reasonCode = $post['__reason_code'] ?? '';
unset($post['__replace_submission_id'], $post['__delete_reason'], $post['__reason_code'], $post['reason_field_code']);
Log::debug(print_r($post, true));
$finfo = LambdaRequest::postrequest('form', 'formentity/submitform', ['formdata' => $post, 'entity_id' => $formdata->form_id, 'user_id' => $formdata->user_id, 'partition' => $formdata->partition ]);
} catch (\Throwable $e) {
@ -5892,6 +5897,19 @@ class FormController extends Controller
Log::debug(print_r($finfo, true));
if($finfo->status == 'success') {
if ($replaceSubmissionId !== '') {
$marked = $this->markMotherForumSubmissionDeleted(
$formdata->partition,
$formdata->user_id,
$formdata->form_id,
$replaceSubmissionId,
$reasonCode,
$deleteReason
);
if (!$marked) {
return response()->json(['status' => 'failure', 'message' => 'The new entry was saved, but the previous entry could not be marked Deleted.']);
}
}
if ($inceptAllocation && $inceptUsageId) {
$submissionId = $finfo->data->submission->id ?? '';
$inceptAllocation->attachSubmission($inceptUsageId, $submissionId);
@ -8009,8 +8027,115 @@ class FormController extends Controller
return response()->json($allapprovers->data->approvals);
}
protected function markMotherForumSubmissionDeleted($partition, $userId, $formId, $submissionId, $reasonCode, $reason): bool
{
$loaded = LambdaRequest::postrequest('form', 'formentity/getformdefination', [
'form_id' => $formId,
'user_id' => $userId,
'partition' => $partition,
'id' => $submissionId,
]);
$previous = json_decode(json_encode($loaded->data->formdata ?? new \stdClass()), true);
if (!is_array($previous) || $previous === []) {
Log::error('Mother Forum previous submission was not loaded: '.$submissionId);
return false;
}
unset($previous['title']);
$previous['id'] = $submissionId;
$previous['form_id'] = $formId;
$previous['record_status'] = 'Deleted';
if ($reasonCode !== '') {
$previous[$reasonCode] = $reason;
}
$saved = LambdaRequest::postrequest('form', 'formentity/submitform', [
'formdata' => $previous,
'entity_id' => $formId,
'user_id' => $userId,
'partition' => $partition,
]);
return ($saved->status ?? '') === 'success';
}
protected function processformdata($form, $post, $user_id = 0, $partition = '' ) {
switch($form) {
case '1791539482-b06c2f9a-fe45-4b16-843a-224f07ed0d7b':
$reasonCode = trim((string) ($post['reason_field_code'] ?? ''));
unset($post['reason_field_code']);
$post['record_status'] = 'Active';
if (empty($post['id'])) {
$post['base_submission_id'] = '';
break;
}
$previousId = $post['id'];
$loaded = LambdaRequest::postrequest('form', 'formentity/getformdefination', [
'form_id' => $form,
'user_id' => $user_id,
'partition' => $partition,
'id' => $previousId,
]);
$previous = json_decode(json_encode($loaded->data->formdata ?? new \stdClass()), true);
if (!is_array($previous) || $previous === []) {
$post['__error'] = 'Unable to load the entry being edited.';
break;
}
$previousStatus = trim((string) ($previous['record_status'] ?? 'Active'));
if (strcasecmp($previousStatus, 'Deleted') === 0) {
$post['__error'] = 'This entry has been replaced and can no longer be edited.';
break;
}
$reason = trim((string) ($reasonCode !== '' ? ($post[$reasonCode] ?? '') : ''));
if ($reason === '') {
$post['__error'] = 'Enter the reason for deletion.';
break;
}
$baseId = trim((string) ($previous['base_submission_id'] ?? ''));
if ($baseId === '') {
$baseId = $previousId;
}
unset($post['id']);
$post['record_status'] = 'Active';
$post['base_submission_id'] = $baseId;
if ($reasonCode !== '') {
$post[$reasonCode] = '';
}
$post['__replace_submission_id'] = $previousId;
$post['__delete_reason'] = $reason;
$post['__reason_code'] = $reasonCode;
break;
case '1756965702-68add159-7312-476d-8c92-0c7683015351':
if (($post['chemist_list'] ?? '') !== 'Others') {
break;
}
$chemistName = trim((string) ($post['cheemist_name'] ?? ''));
if ($chemistName === '') {
$post['__error'] = 'Enter the chemist name.';
break;
}
list($application, $domain) = explode("-", $partition);
$token = DB::table('users')->select("access_token")->where("domain", $domain)->where("permissions", 'like', '%contact_create%')->orderBy('updated_at', 'desc')->skip(0)->take(1)->get();
if ($token->isEmpty() || empty($token[0]->access_token)) {
$post['__error'] = 'Unable to create the chemist. Please try again.';
break;
}
$contactdata = [
'domain' => $domain,
'application' => $application,
'user_id' => $user_id,
'name' => $chemistName,
'type' => 'chemist',
'msl_code' => strtotime("now").'-'.rand(1000,9999),
'report_smartian_pob_other_chemists' => 'Yes',
];
$contact = LambdaRequest::postrequest('contact', 'contact/create', $contactdata, $token[0]->access_token);
$chemistId = $contact->data->item->id ?? null;
if (empty($chemistId)) {
Log::error('Other chemist create failed: '.print_r($contact, true));
$post['__error'] = 'Unable to create the chemist. Please try again.';
break;
}
$post['chemist_list'] = $chemistId;
break;
case '1711609582-e93dba24-9c98-43e7-b97a-b22d64d9b9fc':
case '1711609582-e93dba24-9c98-43e7-neb2-b22d64d9b9fc':
list($application, $domain) = explode("-", $partition);

View File

@ -170,13 +170,15 @@ class SynchrobreatheReportcardController extends Controller
protected function loadTerritories(string $token, ?string &$error): array
{
$error = null;
$params = [];
if ($this->hasAllDataAccess($this->tokenUser)) {
$params['positions'] = 1;
}
try {
$response = LambdaRequest::getrequest('ums', 'hierarchy/get_all', $params, $token, 60);
$response = LambdaRequest::getrequest(
$this->_service,
$this->_module . '/synchrobreatheReport_card_territories',
[],
$token,
60
);
} catch (\Throwable $e) {
$error = 'Failed to load territories.';
@ -191,7 +193,29 @@ class SynchrobreatheReportcardController extends Controller
return [];
}
return $this->territoryOptions($this->hierarchyNodes($response));
$data = is_object($response) ? ($response->data ?? null) : null;
$items = is_object($data) ? ($data->territories ?? []) : [];
if (!is_array($items)) {
return [];
}
$options = [];
foreach ($items as $item) {
if (!is_object($item)) {
continue;
}
$id = trim((string) ($item->id ?? ''));
if ($id === '') {
continue;
}
$label = trim((string) ($item->label ?? ''));
$options[] = [
'id' => $id,
'label' => $label !== '' ? $label : $id,
];
}
return $options;
}
protected function fetchReport(
@ -237,71 +261,6 @@ class SynchrobreatheReportcardController extends Controller
return $this->presentReport($data, $comment, $rbm, $zbm, $marketing);
}
/**
* @param array<int, object> $nodes
* @return array<int, array{id:string,label:string}>
*/
protected function territoryOptions(array $nodes): array
{
$parentIds = [];
foreach ($nodes as $node) {
$parentId = trim((string) ($node->parent_id ?? ''));
if ($parentId !== '' && strtolower($parentId) !== 'root') {
$parentIds[$parentId] = true;
}
}
$options = [];
foreach ($nodes as $node) {
$id = trim((string) ($node->id ?? ''));
if ($id === '' || isset($parentIds[$id]) || $this->isVacant($node)) {
continue;
}
$owner = $this->ownerName($node);
$territory = $this->formatHierarchyName($node->name ?? '');
$label = $owner !== '' ? $owner . ' - ' . $territory : $territory;
$options[] = [
'id' => $id,
'label' => $label,
];
}
usort($options, function ($a, $b) {
return strcasecmp($a['label'], $b['label']);
});
return $options;
}
/**
* @return array<int, object>
*/
protected function hierarchyNodes($response): array
{
$data = is_object($response) ? ($response->data ?? null) : null;
if (!is_object($data)) {
return [];
}
$nodes = [];
$items = $data->items ?? [];
if (is_array($items) || $items instanceof \Traversable) {
foreach ($items as $item) {
if (is_object($item) && !empty($item->id)) {
$nodes[(string) $item->id] = $item;
}
}
}
$me = $data->me ?? null;
if (is_object($me) && !empty($me->id)) {
$nodes[(string) $me->id] = $me;
}
return array_values($nodes);
}
protected function presentReport(object $data, string $comment, string $rbm, string $zbm, string $marketing): array
{
$info = is_object($data->tm_info ?? null) ? $data->tm_info : (object) [];
@ -431,66 +390,6 @@ class SynchrobreatheReportcardController extends Controller
return number_format($number, 2);
}
protected function formatHierarchyName(?string $name): string
{
$name = trim((string) $name);
$stripped = preg_replace('/_[^_]+$/', '', $name);
if (is_string($stripped) && $stripped !== '') {
return $stripped;
}
return $name !== '' ? $name : 'Vacant';
}
protected function ownerName(object $node): string
{
$info = $node->owner_info ?? null;
if (is_object($info)) {
$name = trim((string) ($info->name ?? ''));
if ($name === '') {
$name = trim(((string) ($info->firstname ?? '')) . ' ' . ((string) ($info->lastname ?? '')));
}
if ($name !== '') {
return $name;
}
}
return '';
}
protected function isVacant(object $node): bool
{
$id = strtolower(trim((string) ($node->id ?? '')));
if ($id === '' || $id === 'vacant') {
return true;
}
$owner = strtolower(trim((string) ($node->owner ?? '')));
if ($owner === '' || $owner === 'vacant') {
return true;
}
$flag = $node->is_vacant ?? false;
if ($flag === true || $flag === 1 || $flag === '1' || $flag === 'true') {
return true;
}
$status = strtolower(trim((string) ($node->status ?? 'active')));
return $status !== '' && $status !== 'active';
}
protected function hasAllDataAccess(?object $user): bool
{
if (!$user) {
return false;
}
$flag = $user->all_data_access ?? false;
return $flag === true || $flag === 1 || $flag === '1' || $flag === 'true';
}
protected function unwrapResponse($response)
{
if (!is_object($response)) {

View File

@ -220,10 +220,16 @@ class ZenovaReportController extends Controller
}
$demo = (array) ($row->{'Demography & Details'} ?? []);
$ownName = $this->rowName($row);
foreach ($demo as $key => $value) {
if (strcasecmp((string) $key, 'Name of Zone') === 0 && $value !== null && $value !== '') {
return (string) $value;
if (strcasecmp((string) $key, 'Name of Zone') !== 0 || $value === null || $value === '') {
continue;
}
if (strcasecmp((string) $value, $ownName) === 0) {
continue;
}
return (string) $value;
}
return '';

View File

@ -0,0 +1,200 @@
@extends('layouts.vertical', ['page_title' => $title ?? 'Adherence Pack POB Drive'])
@section('css')
<style>
.pob-table-wrap {
overflow: auto;
max-height: 70vh;
}
.pob-table {
font-size: 12px;
white-space: nowrap;
}
.pob-table th,
.pob-table td {
vertical-align: middle;
}
.pob-table thead tr:nth-child(1) th {
position: sticky;
top: 0;
z-index: 3;
background: #fff;
}
.pob-table thead tr:nth-child(2) th {
position: sticky;
top: 28px;
z-index: 2;
background: #fff;
}
.pob-table td.pob-day-w,
.pob-table thead tr th.pob-day-w {
background: #e2efda;
color: inherit;
}
.pob-table td.pob-day-h,
.pob-table thead tr th.pob-day-h {
background: #d9d9d9;
color: #808080;
}
.pob-table thead tr th.pob-identity {
background: #c00000;
color: #fff;
}
.pob-table thead tr th.pob-summary {
background: #8a1538;
color: #fff;
}
.pob-table td.pob-vacant {
background: #fce4e4;
color: #c00000;
}
.pob-table td.pob-achieved {
background: #c6efce;
color: #006100;
font-weight: 700;
}
</style>
@endsection
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<h4 class="page-title">{{ $title }}</h4>
<p class="text-muted">Adherence Pack POB of at least ₹15,000. Changing the month reloads the report.</p>
@if (!empty($reportError))
<div class="alert alert-danger" role="alert">{{ $reportError }}</div>
@endif
<form method="get" action="{{ $base_url }}/report" id="pob-report-form" class="row g-2 align-items-end">
<div class="col-md-4">
<label for="field-month" class="form-label">Month</label>
<select name="month" id="field-month" class="form-select">
@foreach ($months as $month)
<option value="{{ $month }}"@if (($selectedMonth ?? '') === $month) selected @endif>{{ $month }}</option>
@endforeach
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary" id="pob-show">Show report</button>
</div>
@if (!empty($sections))
<div class="col-auto">
<a class="btn btn-outline-primary" href="{{ $base_url }}/export?month={{ urlencode($selectedMonth) }}">Download Excel</a>
</div>
@endif
</form>
<p class="text-muted mt-2 d-none" id="pob-loading">Loading the report. This can take a minute.</p>
</div>
</div>
@if (!empty($sections))
@php
$tabLabels = [
'abm' => 'Details ABM wise',
'region' => 'Region Daily Winners',
'territory' => 'Territory Daily Winners',
];
@endphp
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs" role="tablist">
@foreach ($sections as $index => $section)
<li class="nav-item" role="presentation">
<button class="nav-link{{ $index === 0 ? ' active' : '' }}" id="pob-tab-{{ $section['id'] }}" data-bs-toggle="tab" data-bs-target="#pob-pane-{{ $section['id'] }}" type="button" role="tab" aria-controls="pob-pane-{{ $section['id'] }}" aria-selected="{{ $index === 0 ? 'true' : 'false' }}">{{ $tabLabels[$section['id']] ?? $section['title'] }}</button>
</li>
@endforeach
</ul>
<div class="tab-content pt-3">
@foreach ($sections as $index => $section)
@php
$firstDay = null;
$lastDay = null;
foreach ($section['dayTypes'] as $dayIndex => $dayType) {
if ($dayType === 'W' || $dayType === 'H') {
if ($firstDay === null) {
$firstDay = $dayIndex;
}
$lastDay = $dayIndex;
}
}
@endphp
<div class="tab-pane fade{{ $index === 0 ? ' show active' : '' }}" id="pob-pane-{{ $section['id'] }}" role="tabpanel" aria-labelledby="pob-tab-{{ $section['id'] }}">
<div class="pob-table-wrap">
<table class="table table-bordered table-sm pob-table mb-0">
<thead>
<tr>
@foreach ($section['dayTypes'] as $dayType)
<th class="{{ $dayType === 'H' ? 'pob-day-h' : ($dayType === 'W' ? 'pob-day-w' : '') }}">{{ $dayType }}</th>
@endforeach
</tr>
<tr>
@foreach ($section['headers'] as $headerIndex => $header)
@php
$dayType = $section['dayTypes'][$headerIndex] ?? '';
$headerClass = $dayType === 'H' ? 'pob-day-h' : ($dayType === 'W' ? 'pob-day-w' : (($firstDay !== null && $headerIndex < $firstDay) ? 'pob-identity' : 'pob-summary'));
@endphp
<th class="{{ $headerClass }}">{!! nl2br(e($header)) !!}</th>
@endforeach
</tr>
</thead>
<tbody>
@forelse ($section['rows'] as $row)
<tr>
@foreach ($row['cells'] as $cellIndex => $cell)
@php
$dayType = $section['dayTypes'][$cellIndex] ?? '';
$cellClass = '';
if ($dayType === 'H' || $cell === 'Holiday') {
$cellClass = 'pob-day-h';
} elseif ($dayType === 'W') {
$cellClass = 'pob-day-w';
} elseif (!empty($row['vacant']) && $firstDay !== null && $cellIndex < $firstDay) {
$cellClass = 'pob-vacant';
} elseif ($cell === 'Achieved') {
$cellClass = 'pob-achieved';
}
@endphp
<td class="{{ $cellClass }}">{{ $cell }}</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="{{ count($section['headers']) }}">No rows for this month.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
</div>
</div>
</div>
@endsection
@section('script')
<script>
document.getElementById('pob-report-form').addEventListener('submit', function () {
document.getElementById('pob-loading').classList.remove('d-none');
document.getElementById('pob-show').disabled = true;
});
</script>
@endsection

View File

@ -782,7 +782,7 @@ if (!empty($submissions->data->submissions)) {
Swal.fire({
icon: 'error',
title: 'Data not submitted',
text: 'Something went wrong, please try again after some time!'
text: (data && data.message) ? data.message : 'Something went wrong, please try again after some time!'
})
}
},

View File

@ -112,7 +112,7 @@ $(function () {
var surveyContacts = [];
var submittedIdentities = [];
var contactsReady = false;
var unlockAllSections = false;
var unlockAllSections = Boolean($('#field-submission_id').val());
var savedAnswers = @json($savedAnswers);
var photoFields = ['visiting_card', 'rx_pad', 'doctor_board', 'chemist_board'];
@ -558,6 +558,12 @@ $(function () {
$(this).closest('.stockist-block').find('.stockist-name').val(this.value);
});
$('#field-visit_type').on('change', function () { setVisit(this.value); });
$('#doctor-fields').on('input', '#field-no_of_staff', function () {
var digits = String(this.value).replace(/\D/g, '').slice(0, 3);
if (this.value !== digits) {
this.value = digits;
}
});
$('#report-form').on('input change', '.survey-section :input', refreshSections);
$('#chemist-fields').on('change', '.rcpa-doctor', function () { applyDoctor(this); refreshSections(); });
$('#chemist-fields').on('change', '.stockist-pick', function () { applyStockist(this); });
@ -584,6 +590,10 @@ $(function () {
return /^[A-Za-z0-9]+(?:[ ]+[A-Za-z0-9]+)*$/.test(value);
}
function stockistAddressOk(value) {
return /^(?=.*[A-Za-z0-9])[A-Za-z0-9 ,.\-\/#&()']+$/.test(value);
}
function doctorAddressOk(value) {
return /^[A-Za-z0-9]+(?:[ ,]+[A-Za-z0-9]+)*$/.test(value);
}
@ -718,6 +728,11 @@ $(function () {
fail('Enter the other specialty in letters only.');
return;
}
var staffCount = $('#field-no_of_staff').val().trim();
if (staffCount !== '' && !/^\d{1,3}$/.test(staffCount)) {
fail('No. of Staff can be up to 3 digits only.');
return;
}
var missingUnits = false;
$('#doctor-fields .product-choice').each(function () {
if ($(this).val() !== 'Yes') {
@ -773,7 +788,8 @@ $(function () {
fail('A chemist with this mobile number already exists.');
return;
}
var badStockist = false;
var badStockistName = false;
var badStockistAddress = false;
$('#chemist-fields .stockist-pick').each(function () {
if (this.value !== 'others') {
return;
@ -782,12 +798,19 @@ $(function () {
var otherName = block.find('.stockist-other-name').val().trim();
var otherAddress = block.find('.stockist-location').val().trim();
block.find('.stockist-name').val(otherName);
if (!addressOk(otherName) || !addressOk(otherAddress)) {
badStockist = true;
if (!addressOk(otherName)) {
badStockistName = true;
}
if (!stockistAddressOk(otherAddress)) {
badStockistAddress = true;
}
});
if (badStockist) {
fail('Other stockist name and address can contain letters, numbers and spaces only.');
if (badStockistName) {
fail('Other stockist name can contain letters, numbers and spaces only.');
return;
}
if (badStockistAddress) {
fail('Other stockist address can contain letters, numbers, spaces and symbols such as comma, period, hyphen, slash, hash, ampersand, parentheses and apostrophe.');
return;
}
if (!therapiesFilled('#chemist-fields')) {
@ -842,6 +865,7 @@ $(function () {
$('#report-form a').filter(function () { return $(this).text() === 'View current photo'; }).remove();
$('#report-form')[0].reset();
$('#field-submission_id').val('').attr('value', '');
unlockAllSections = false;
$('#field-visit_type').val('doctor');
setVisit('doctor');
loadContacts();

View File

@ -85,7 +85,6 @@
<tr>
<th>Action</th>
<th>Chemist Name</th>
<th>Speciality</th>
<th>MSL Code</th>
</tr>
</thead>
@ -187,7 +186,7 @@
<div id="actual-section" class="d-none">
<h5 class="mt-3">Actual Values</h5>
<div class="mb-2">
<label class="form-label" for="field-upload_invoice">Upload Invoice <span class="text-danger">*</span></label>
<label class="form-label" for="field-upload_invoice">Upload Invoice</label>
<input type="file" class="form-control" id="field-upload_invoice" name="upload_invoice" accept=".jpg,.jpeg,.png,.pdf,image/jpeg,image/png,application/pdf">
</div>
<div class="table-responsive">
@ -358,12 +357,34 @@
function setActualVisible(show) {
$('#actual-section').toggleClass('d-none', !show);
$('#actual-section').find('input').prop('disabled', !show);
$('#field-upload_invoice').prop('required', !!show);
$('#field-upload_invoice').prop('required', false);
if (show) {
recalc('.actual-units', '.actual-value', '#field-total_actual_pob_units', '#field-total_actual_pob_value');
}
}
function unlockReportUnits() {
$('#report-form input[data-locked-unit]').remove();
$('.pob-units').each(function () {
var input = $(this);
var name = input.attr('data-unit-name') || input.attr('name');
if (name) input.attr('name', name);
input.prop('disabled', false).prop('readonly', false);
});
}
function lockReportUnits() {
unlockReportUnits();
$('.pob-units').each(function () {
var input = $(this);
var name = input.attr('name');
if (!name) return;
input.attr('data-unit-name', name);
$('<input type="hidden">').attr('data-locked-unit', '1').attr('name', name).val(input.val()).insertAfter(input);
input.prop('disabled', true).removeAttr('name');
});
}
function showReport(type, contactId) {
$('#tabs-wrapper').addClass('d-none');
$('#report-panel').removeClass('d-none');
@ -371,6 +392,7 @@
$('#report-title').text('POB Details (Without Visit)');
$('#contact-label').html((type === 'ebt_chemist' ? 'Select Chemist' : 'Select Doctor') + ' <span class="text-danger">*</span>');
$('#report-form')[0].reset();
unlockReportUnits();
$('#field-submission-id').val('');
$('#field-record_date').prop('readonly', false);
$('#field-contact_type').val(type);
@ -394,6 +416,7 @@
$('#report-title').text(mode === 'upload' ? 'Upload Invoice' : 'Edit Order');
$('#contact-label').html((type === 'ebt_chemist' ? 'Select Chemist' : 'Select Doctor') + ' <span class="text-danger">*</span>');
$('#report-form')[0].reset();
unlockReportUnits();
$('#field-submission-id').val(submission.id || '');
$('#field-contact_type').val(type);
initContactSearch(type);
@ -412,6 +435,7 @@
});
recalc('.pob-units', '.pob-value', '#field-total_pob_units', '#field-total_pob_value');
setActualVisible($('#field-supply_status').val() === 'Yes');
lockReportUnits();
openContactId = data.contact_id || '';
}
@ -428,24 +452,28 @@
var child = document.createElement('tr');
child.className = 'pob-child';
child.setAttribute('data-open', 'yes');
child.innerHTML = '<td colspan="4">Loading...</td>';
var colSpan = row.closest('table') && row.closest('table').id === 'chemist-table' ? 3 : 4;
child.innerHTML = '<td colspan="' + colSpan + '">Loading...</td>';
row.after(child);
$.getJSON('/form/pobhistory', { form_id: formId, contact_id: contactId }, function (data) {
var submissions = (data && data.submissions) || [];
var orderRows = submissions.map(function (submission) {
var info = submission.formdata || {};
var open = info.submission_status === 'Order Taken' || info.submission_status === 'Rejected';
var button = open
? '<button type="button" class="btn btn-warning btn-xs edit-order me-1" data-submission="' + esc(JSON.stringify(submission)) + '">Edit</button>'
+ '<button type="button" class="btn btn-warning btn-xs upload-invoice" data-submission="' + esc(JSON.stringify(submission)) + '">Upload Invoice</button>'
: '';
var payload = esc(JSON.stringify(submission));
var button = '';
var workflowStatus = String(submission.workflow_status || '').toLowerCase();
if (info.supply_status === 'No' && info.submission_status === 'Order Taken') {
button = '<button type="button" class="btn btn-warning btn-xs edit-order" data-submission="' + payload + '">Edit</button>';
} else if (workflowStatus === 'rejected') {
button = '<button type="button" class="btn btn-warning btn-xs upload-invoice" data-submission="' + payload + '">Upload Invoice</button>';
}
return '<tr><td>' + button + '</td><td>' + esc(info.record_date || '') + '</td><td>' + esc(info.submission_status || '') + '</td><td>' + esc(info.total_pob_value || '') + '</td><td>' + esc(info.total_actual_pob_value || '') + '</td></tr>';
}).join('');
child.innerHTML = '<td colspan="4" class="p-0"><div class="table-responsive"><table class="table table-sm mb-0 pob-orders"><thead class="table-light"><tr><th>Action</th><th>Date</th><th>Status</th><th>Total Value</th><th>Actual Total</th></tr></thead><tbody>'
child.innerHTML = '<td colspan="' + colSpan + '" class="p-0"><div class="table-responsive"><table class="table table-sm mb-0 pob-orders"><thead class="table-light"><tr><th>Action</th><th>Date</th><th>Status</th><th>Total Value</th><th>Actual Total</th></tr></thead><tbody>'
+ (orderRows || '<tr><td colspan="5" class="text-muted">No orders yet.</td></tr>')
+ '</tbody></table></div></td>';
}).fail(function () {
child.innerHTML = '<td colspan="4" class="text-danger">Could not load orders.</td>';
child.innerHTML = '<td colspan="' + colSpan + '" class="text-danger">Could not load orders.</td>';
});
}
@ -453,8 +481,9 @@
var body = document.querySelector('#' + tableId + ' tbody');
var rows = contacts[type] || [];
body.innerHTML = '';
var chemist = type === 'ebt_chemist';
if (!rows.length) {
body.innerHTML = '<tr><td colspan="4" class="text-muted">No ' + (type === 'ebt_doctor' ? 'doctors' : 'chemists') + ' yet.</td></tr>';
body.innerHTML = '<tr><td colspan="' + (chemist ? 3 : 4) + '" class="text-muted">No ' + (chemist ? 'chemists' : 'doctors') + ' yet.</td></tr>';
return;
}
rows.forEach(function (item) {
@ -463,7 +492,7 @@
tr.setAttribute('data-contact', item.value);
tr.innerHTML = '<td><button type="button" class="btn btn-info btn-xs report-btn">Report</button></td>'
+ '<td class="pob-toggle"><i class="mdi mdi-arrow-down-drop-circle"></i> ' + esc(item.display || contact.name || '') + '</td>'
+ '<td>' + esc(contact.speciality || '') + '</td>'
+ (chemist ? '' : '<td>' + esc(contact.speciality || '') + '</td>')
+ '<td>' + esc(contact.msl_code || '') + '</td>';
tr.querySelector('.report-btn').addEventListener('click', function () {
showReport(type, item.value);
@ -515,7 +544,12 @@
var contactSelect = $('#field-contact_id');
contactSelect.prop('disabled', false);
openContactId = contactSelect.val() || openContactId;
$('#report-form [name="submission_status"]').val($('#field-supply_status').val() === 'Yes' ? 'Invoice Uploaded' : 'Order Taken');
var supplyYes = $('#field-supply_status').val() === 'Yes';
var invoiceFile = document.getElementById('field-upload_invoice');
var hasInvoice = invoiceFile && invoiceFile.files && invoiceFile.files.length > 0;
var submissionStatus = 'Order Taken';
if (supplyYes) submissionStatus = hasInvoice ? 'Invoice Uploaded' : 'Submitted';
$('#report-form [name="submission_status"]').val(submissionStatus);
var payload = new FormData(form);
contactSelect.prop('disabled', true);
$('#pageloader').css('display', 'flex').hide().fadeIn();

View File

@ -1,5 +1,5 @@
@php
$chemistTypes = ['Attached','Floating','Semi','Chain Pharmacy'];
$chemistTypes = ['Attached','Floating','Semi/Sub-Stockists','Chain Pharmacy'];
$products = ['Asthalin Inhaler' => 'asthalin_inhaler','Asthalin Rotacap' => 'asthalin_rotacap','Aerocort Inhaler' => 'aerocort_inhaler','Aerocort Rotacap' => 'aerocort_rotacap','Aerocort Forte Rotacap' => 'aerocort_forte_rotacap'];
$therapies = [
'Nebulization No. of Respules per Month (all included)' => 'neb_respules',
@ -8,7 +8,7 @@ $therapies = [
'Asthalin Syrup No. of Units per Month' => 'asthalin_syrup_units',
'Levolin Syrup No. of Units per Month' => 'levolin_syrup_units',
'Deriphyllin No. of Units per Month' => 'deriphyllin_units',
'Beclomethasone Tablet No. of Units per Month' => 'beclomethasone_units',
'Betnesol Tablet No. of Units per Month' => 'beclomethasone_units',
'Methylprednisolone Tablet No. of Units per Month' => 'methylprednisolone_units',
'Prednisolone Tablet No. of Units per Month' => 'prednisolone_units',
'Cough Syrups No. of Units per Month' => 'cough_syrups_units',

View File

@ -1,5 +1,5 @@
@php
$degrees = ['B. Pharm','D. Pharm','M.B.B.S.','M.D.','B.A.M.S.','B.U.M.S.','B.H.M.S.','D.A.M.S.','D.H.M.S.','B.M.S.','D.M.S.','B.E.M.S.','R.M.P.','B.I.A.M.','D.I.A.M.','C.M.S.','C.M.S. & E.D.','C.C.M.P.','G.A.M.S.','B.P.T.','B.S.C.','Other'];
$degrees = ['B. Pharm','D. Pharm','M.B.B.S.','M.D.','B.A.M.S.','B.U.M.S.','B.H.M.S.','D.A.M.S.','D.H.M.S.','B.M.S.','D.M.S.','B.E.M.S.','R.M.P.','B.I.A.M.','D.I.A.M.','C.M.S.','C.M.S. & E.D.','C.C.M.P.','G.A.M.S.','B.P.T.','B.S.C.','D.C.H.','Other'];
$specialties = ['G.P (General Physician)','R.M.P (Registered Medical Practitioner)','C.P (Consulting Physician)','Child Specialist','Pediatrician','Family Physician','Multi Speciality','Gynecologist','Urologist','Physiotherapy','Other'];
$bands = ['0 to 10','10 to 20','20 to 30','30 to 40','40 to 50','More than 50'];
$doctorTypes = ['Standalone Clinic','Nursing Home','Hospital','Dispensing Doctors (Doctor with Self Purchase)'];
@ -30,7 +30,7 @@ $products = ['Asthalin Inhaler' => 'asthalin_inhaler','Asthalin Rotacap' => 'ast
<button type="button" class="section-head">Chemist details</button>
<div class="section-body">
<div class="mb-2"><label class="form-label">Attached Chemist Name (If any)</label><input type="text" class="form-control" name="attached_chemist_name"></div>
<div class="mb-2"><label class="form-label">No. of Staff</label><input type="number" min="0" step="1" class="form-control" name="no_of_staff"></div>
<div class="mb-2"><label class="form-label">No. of Staff</label><input type="number" min="0" max="999" step="1" inputmode="numeric" class="form-control" id="field-no_of_staff" name="no_of_staff"></div>
</div>
</div>
@ -62,7 +62,7 @@ $products = ['Asthalin Inhaler' => 'asthalin_inhaler','Asthalin Rotacap' => 'ast
'Asthalin Syrup No. of Units per Month' => 'asthalin_syrup_units',
'Levolin Syrup No. of Units per Month' => 'levolin_syrup_units',
'Deriphyllin No. of Units per Month' => 'deriphyllin_units',
'Beclomethasone Tablet No. of Units per Month' => 'beclomethasone_units',
'Betnesol Tablet No. of Units per Month' => 'beclomethasone_units',
'Methylprednisolone Tablet No. of Units per Month' => 'methylprednisolone_units',
'Prednisolone Tablet No. of Units per Month' => 'prednisolone_units',
'Cough Syrups No. of Units per Month' => 'cough_syrups_units',

View File

@ -61,11 +61,35 @@
$isEditMode = !empty($id);
$reasonQuestionCode = '';
foreach ($questions as $question) {
$label = trim(preg_replace('/\s+/', ' ', strip_tags($question->question ?? '')));
if (stripos($label, 'Reason for Deletion') !== false) {
$reasonQuestionCode = $question->code;
break;
}
}
$savedFormdata = (isset($form->formdata) && is_object($form->formdata)) ? $form->formdata : null;
$recordStatus = ($savedFormdata && isset($savedFormdata->record_status)) ? (string) $savedFormdata->record_status : '';
$isDeletedEntry = $isEditMode && strcasecmp($recordStatus, 'Deleted') === 0;
$baseSubmissionId = ($savedFormdata && !empty($savedFormdata->base_submission_id)) ? (string) $savedFormdata->base_submission_id : '';
@endphp
@if($isDeletedEntry)
<div class="col-12">
<div class="alert alert-warning mb-0">This entry has been replaced and can no longer be edited.</div>
</div>
@else
<form method="POST" action="/form/submit" id="report-form" enctype="multipart/form-data">
<input type="hidden" name="form_id" value="{{$form_id}}">
<input type="hidden" name="user_id" value="{{$form->user_id}}">
<input type="hidden" name="record_status" value="Active">
<input type="hidden" name="base_submission_id" value="{{ $baseSubmissionId }}">
@if($reasonQuestionCode !== '')
<input type="hidden" name="reason_field_code" value="{{ $reasonQuestionCode }}">
@endif
@csrf
@if($id)
@ -73,8 +97,16 @@
@endif
@foreach($questions as $question)
@if(in_array($question->code, ['record_status', 'base_submission_id'], true))
@continue
@endif
@php
$isReasonField = $reasonQuestionCode !== '' && $question->code === $reasonQuestionCode;
$fieldRequired = $isReasonField ? $isEditMode : ($question->required == 'yes');
$fieldDisabled = $isReasonField && !$isEditMode;
@endphp
<div class="col-md-12 mb-2">
<label for="field-{{$question->code}}" class="form-label">{{$question->question}} {!!$question->required == 'yes' ? '<span class="text-danger">*</span>':''!!} </label>
<label for="field-{{$question->code}}" class="form-label">{{$question->question}} {!!$fieldRequired ? '<span class="text-danger">*</span>':''!!} </label>
@switch($question->type)
@case('hidden')
<input type="{{$question->type}}" id="field-{{$question->code}}" name="{{$question->code}}" value="{{isset($question->value)?$question->value:''}}">
@ -83,13 +115,13 @@
@case('email')
@case('tel')
@case('date')
<input type="{{$question->type}}" id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{$question->required == 'yes'?'required':'' }} value="{{isset($question->value)?$question->value:''}}">
<input type="{{$question->type}}" id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{ $fieldRequired ? 'required' : '' }} {{ $fieldDisabled ? 'disabled' : '' }} value="{{isset($question->value)?$question->value:''}}">
@break
@case('number')
<input type="{{$question->type}}" min="0" max="9999999999" step="1" id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{$question->required == 'yes'?'required':'' }} value="{{isset($question->value)?$question->value:0}}">
<input type="{{$question->type}}" min="0" max="9999999999" step="1" id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{ $fieldRequired ? 'required' : '' }} {{ $fieldDisabled ? 'disabled' : '' }} value="{{isset($question->value)?$question->value:0}}">
@break
@case('textarea')
<textarea id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{$question->required == 'yes'?'required':'' }}>{{isset($question->value)?$question->value:''}}</textarea>
<textarea id="field-{{$question->code}}" name="{{$question->code}}" class="form-control" {{ $fieldRequired ? 'required' : '' }} {{ $fieldDisabled ? 'disabled' : '' }}>{{isset($question->value)?$question->value:''}}</textarea>
@break
@ -124,7 +156,7 @@
@break --}}
@case('dropdown')
<select class="form-control" id="field-{{ $question->code }}" name="{{ $question->code }}" data-initial-value="{{ isset($question->value) ? $question->value : '' }}" {{ $question->required == 'yes' ? 'required' : '' }}>
<select class="form-control" id="field-{{ $question->code }}" name="{{ $question->code }}" data-initial-value="{{ isset($question->value) ? $question->value : '' }}" {{ $fieldRequired ? 'required' : '' }} {{ $fieldDisabled ? 'disabled' : '' }}>
<option value=""> -- Select -- </option>
@foreach ($question->options as $option)
@php
@ -204,6 +236,7 @@
</div>
@endif
</form>
@endif
</div> <!-- end card-body -->
@ -481,7 +514,7 @@
Swal.fire({
icon: 'error',
title: 'Data not submitted',
text: 'Something went wrong, please try again after some time!'
text: (data && data.message) ? data.message : 'Something went wrong, please try again after some time!'
})
}
},

View File

@ -175,6 +175,14 @@
</a>
</li>
{{-- @endif --}}
@if(\App\Http\Controllers\AdherencepobdrivereportController::canDownload(auth()->user()))
<li>
<a href="/adherencepobdrivereport/report">
<i data-feather="file-text"></i>
<span>Adherence Pack POB Drive</span>
</a>
</li>
@endif
@endif
@if(auth()->user()->domain == 'gennovadc')

View File

@ -114,6 +114,7 @@ use App\Http\Controllers\ForacortchemistavailabilityController;
use App\Http\Controllers\Team2unitsreportController;
use App\Http\Controllers\SynchrobreatheReportcardController;
use App\Http\Controllers\AdherencepackratioreportController;
use App\Http\Controllers\AdherencepobdrivereportController;
use App\Http\Controllers\RevesmartlaunchdriveController;
use App\Http\Controllers\PbsdoctoranalyticsReportController;
use App\Http\Controllers\TitanshcpgatheringreportController;
@ -281,6 +282,7 @@ $modules = ['user' => UserController::class,
'team2unitsreport' => Team2unitsreportController::class,
'synchrobreatheReportcard' => SynchrobreatheReportcardController::class,
'adherencepackratioreport' => AdherencepackratioreportController::class,
'adherencepobdrivereport' => AdherencepobdrivereportController::class,
'revesmartlaunchdrive' => RevesmartlaunchdriveController::class,
'pbsdoctoranalyticsreport' => PbsdoctoranalyticsReportController::class,
'titanshcpgatheringreport' => TitanshcpgatheringreportController::class,