Initial commit: Vendor Sales Report Magento 2 module
This commit is contained in:
141
.github/copilot-instructions.md
vendored
Normal file
141
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
# AI Coding Instructions for Vendor Sales Report Module
|
||||
|
||||
## Project Overview
|
||||
This is a Magento 2 module (`Shopkeeper_VendorSalesReport`) that generates monthly vendor sales reports with automated email delivery. The module provides both manual report generation via admin interface and automated monthly reports via cron.
|
||||
|
||||
## Architecture & Key Components
|
||||
|
||||
### Core Models
|
||||
- **`Model/ReportGenerator.php`**: Handles report data generation using raw SQL queries joining `sales_order`, `sales_order_item`, `sales_order_address`, and product tables. Includes state abbreviation logic and cost fallback from order item to current product cost.
|
||||
- **`Model/EmailSender.php`**: Manages email delivery using Magento's TransportBuilder with CSV attachments.
|
||||
- **`Helper/Data.php`**: Configuration helper using Magento's scope config for settings like order status filters, email recipients, and subject templates.
|
||||
|
||||
### Admin Interface
|
||||
- **Controller Structure**: `Controller/Adminhtml/Report/` with `Index.php` (main page), `Grid.php` (data grid), `Export.php` (CSV download).
|
||||
- **Menu Integration**: Located under `Reports > Sales > Vendor Sales Report` in admin panel.
|
||||
- **Configuration**: System config at `Stores > Configuration > Shopkeeper > Vendor Sales Report` with general and email settings.
|
||||
|
||||
### Automation
|
||||
- **`Cron/GenerateMonthlyReport.php`**: Executes monthly on the 1st at 2:00 AM, generates previous month's report, and emails it.
|
||||
- **Cron Schedule**: `0 2 1 * *` (crontab.xml configuration).
|
||||
|
||||
## Critical Patterns & Conventions
|
||||
|
||||
### Report Generation Logic
|
||||
```php
|
||||
// Always exclude configurable products from reports
|
||||
AND oi.product_type != 'configurable'
|
||||
|
||||
// Cost fallback hierarchy: order item cost -> current product cost -> 0
|
||||
COALESCE(NULLIF(oi.base_cost, 0), current_cost.value, 0)
|
||||
|
||||
// State abbreviation: only abbreviate if full name, leave 2-char codes unchanged
|
||||
protected function abbreviateState($stateName) {
|
||||
if (strlen($stateName) == 2) return $stateName;
|
||||
return $this->stateAbbreviations[$stateName] ?? $stateName;
|
||||
}
|
||||
```
|
||||
|
||||
### CSV Export Format
|
||||
- **Filename**: Always `FF_SAC_730_MCQUEEN.csv` (hardcoded in `ReportGenerator::getFilename()`)
|
||||
- **Columns**: Date, Vendor Product number, Description, Vendor code, Vendor name, Dealer product number, Country, City, State, Zip code, Quantity, Cost
|
||||
- **Date Format**: MM/DD/YYYY
|
||||
- **Country**: "United States" for US orders, otherwise country code
|
||||
- **State**: Abbreviated for US addresses only
|
||||
|
||||
### Configuration Access
|
||||
```php
|
||||
// Use helper for all config access - never access scopeConfig directly in controllers/models
|
||||
$this->configHelper->isEnabled()
|
||||
$this->configHelper->getOrderStatus() // Returns array
|
||||
$this->configHelper->getEmailRecipients() // Returns array
|
||||
$this->configHelper->getEmailSubject($month, $year) // Handles {{month}}/{{year}} replacement
|
||||
```
|
||||
|
||||
### Email Templates
|
||||
- **Template ID**: `vendorsalesreport_email_template`
|
||||
- **Variables**: `subject`, `month`, `year`
|
||||
- **Attachment**: CSV file with `text/csv` MIME type
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Module Installation
|
||||
```bash
|
||||
# Enable module
|
||||
php bin/magento module:enable Shopkeeper_VendorSalesReport
|
||||
php bin/magento setup:upgrade
|
||||
php bin/magento setup:di:compile
|
||||
php bin/magento setup:static-content:deploy -f
|
||||
php bin/magento cache:flush
|
||||
```
|
||||
|
||||
### Testing Cron Jobs
|
||||
```bash
|
||||
# Manual cron execution
|
||||
php bin/magento cron:run --group default
|
||||
|
||||
# Check cron_schedule table
|
||||
SELECT * FROM cron_schedule WHERE job_code = 'shopkeeper_vendorsalesreport_monthly' ORDER BY scheduled_at DESC;
|
||||
```
|
||||
|
||||
### Debugging
|
||||
- **Logs**: Check `var/log/system.log` for module-specific messages
|
||||
- **SQL Queries**: Report generation uses complex joins - examine `ReportGenerator::generateReportData()` for table relationships
|
||||
- **Email Issues**: Verify SMTP configuration and check Magento's email settings
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Data Issues
|
||||
- **Empty Reports**: Check order status filter (default: 'complete') and date ranges
|
||||
- **Missing Costs**: Ensure products have cost attribute set, module falls back gracefully
|
||||
- **State Abbreviations**: Only US states are abbreviated; international addresses remain unchanged
|
||||
|
||||
### Configuration
|
||||
- **Email Recipients**: Must be comma-separated, validated before sending
|
||||
- **Order Status**: Stored as comma-separated string, converted to array by helper
|
||||
- **Subject Templates**: Support `{{month}}` and `{{year}}` placeholders
|
||||
|
||||
### Performance
|
||||
- **Raw SQL**: Used for performance with large datasets - avoid ORM for report queries
|
||||
- **Memory Usage**: CSV generation uses `php://temp` stream to handle large reports
|
||||
- **Cron Timing**: Runs early morning to avoid peak hours
|
||||
|
||||
## Extension Points
|
||||
|
||||
### Adding New Report Columns
|
||||
1. Modify SQL query in `ReportGenerator::generateReportData()`
|
||||
2. Update CSV headers in `ReportGenerator::generateCsv()`
|
||||
3. Ensure column exclusion logic handles new fields appropriately
|
||||
|
||||
### Custom Email Logic
|
||||
1. Extend `EmailSender` class
|
||||
2. Modify email template variables in `email_templates.xml`
|
||||
3. Update template file `view/adminhtml/email/report.html`
|
||||
|
||||
### Additional Filters
|
||||
1. Add new config fields in `system.xml`
|
||||
2. Update `Helper/Data.php` with new getter methods
|
||||
3. Modify SQL WHERE clauses in `ReportGenerator::generateReportData()`
|
||||
|
||||
## File Structure Reference
|
||||
```
|
||||
etc/
|
||||
├── module.xml # Module dependencies
|
||||
├── config.xml # Default configuration values
|
||||
├── crontab.xml # Cron job definitions
|
||||
├── email_templates.xml # Email template registration
|
||||
└── adminhtml/
|
||||
├── menu.xml # Admin menu placement
|
||||
├── routes.xml # URL routing
|
||||
└── system.xml # Configuration UI
|
||||
|
||||
Model/ # Business logic
|
||||
├── ReportGenerator.php # Core report generation
|
||||
└── EmailSender.php # Email delivery
|
||||
|
||||
Controller/Adminhtml/Report/ # Admin interface
|
||||
├── Index.php # Main report page
|
||||
├── Grid.php # Data grid (AJAX)
|
||||
└── Export.php # CSV download
|
||||
```</content>
|
||||
<parameter name="filePath">t:\Files\Documents\BIZ\Clients\McQueen\Repos\VendorSalesReport\.github\copilot-instructions.md
|
||||
78
Block/Adminhtml/Report.php
Normal file
78
Block/Adminhtml/Report.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Block\Adminhtml;
|
||||
|
||||
use Magento\Backend\Block\Template;
|
||||
use Magento\Backend\Block\Template\Context;
|
||||
use Shopkeeper\VendorSalesReport\Helper\Data as ConfigHelper;
|
||||
|
||||
class Report extends Template
|
||||
{
|
||||
/**
|
||||
* @var ConfigHelper
|
||||
*/
|
||||
protected $configHelper;
|
||||
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param ConfigHelper $configHelper
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
ConfigHelper $configHelper,
|
||||
array $data = []
|
||||
) {
|
||||
$this->configHelper = $configHelper;
|
||||
parent::__construct($context, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get grid URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGridUrl()
|
||||
{
|
||||
return $this->getUrl('*/*/grid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get export URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExportUrl()
|
||||
{
|
||||
return $this->getUrl('*/*/export');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured order statuses
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOrderStatuses()
|
||||
{
|
||||
return $this->configHelper->getOrderStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default start date (first day of last month)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultStartDate()
|
||||
{
|
||||
return date('Y-m-01', strtotime('first day of last month'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default end date (last day of last month)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultEndDate()
|
||||
{
|
||||
return date('Y-m-t', strtotime('last day of last month'));
|
||||
}
|
||||
}
|
||||
91
Controller/Adminhtml/Report/Export.php
Normal file
91
Controller/Adminhtml/Report/Export.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Controller\Adminhtml\Report;
|
||||
|
||||
use Magento\Backend\App\Action;
|
||||
use Magento\Backend\App\Action\Context;
|
||||
use Magento\Framework\App\Response\Http\FileFactory;
|
||||
use Magento\Framework\App\Filesystem\DirectoryList;
|
||||
use Shopkeeper\VendorSalesReport\Model\ReportGenerator;
|
||||
use Shopkeeper\VendorSalesReport\Helper\Data as ConfigHelper;
|
||||
|
||||
class Export extends Action
|
||||
{
|
||||
const ADMIN_RESOURCE = 'Shopkeeper_VendorSalesReport::report';
|
||||
|
||||
/**
|
||||
* @var FileFactory
|
||||
*/
|
||||
protected $fileFactory;
|
||||
|
||||
/**
|
||||
* @var ReportGenerator
|
||||
*/
|
||||
protected $reportGenerator;
|
||||
|
||||
/**
|
||||
* @var ConfigHelper
|
||||
*/
|
||||
protected $configHelper;
|
||||
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param FileFactory $fileFactory
|
||||
* @param ReportGenerator $reportGenerator
|
||||
* @param ConfigHelper $configHelper
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
FileFactory $fileFactory,
|
||||
ReportGenerator $reportGenerator,
|
||||
ConfigHelper $configHelper
|
||||
) {
|
||||
parent::__construct($context);
|
||||
$this->fileFactory = $fileFactory;
|
||||
$this->reportGenerator = $reportGenerator;
|
||||
$this->configHelper = $configHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export action
|
||||
*
|
||||
* @return \Magento\Framework\App\ResponseInterface
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
try {
|
||||
$startDate = $this->getRequest()->getParam('start_date');
|
||||
$endDate = $this->getRequest()->getParam('end_date');
|
||||
|
||||
if (!$startDate || !$endDate) {
|
||||
$this->messageManager->addErrorMessage(__('Please select a date range.'));
|
||||
return $this->_redirect('*/*/index');
|
||||
}
|
||||
|
||||
// Get order statuses
|
||||
$orderStatuses = $this->configHelper->getOrderStatus();
|
||||
|
||||
// Generate report data
|
||||
$data = $this->reportGenerator->generateReportData($startDate, $endDate, $orderStatuses);
|
||||
|
||||
if (empty($data)) {
|
||||
$this->messageManager->addWarningMessage(__('No data found for the selected date range.'));
|
||||
return $this->_redirect('*/*/index');
|
||||
}
|
||||
|
||||
// Generate CSV
|
||||
$csvContent = $this->reportGenerator->generateCsv($data);
|
||||
$filename = $this->reportGenerator->getFilename();
|
||||
|
||||
return $this->fileFactory->create(
|
||||
$filename,
|
||||
$csvContent,
|
||||
DirectoryList::VAR_DIR,
|
||||
'text/csv'
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->messageManager->addErrorMessage(__('An error occurred while generating the report: %1', $e->getMessage()));
|
||||
return $this->_redirect('*/*/index');
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Controller/Adminhtml/Report/Grid.php
Normal file
86
Controller/Adminhtml/Report/Grid.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Controller\Adminhtml\Report;
|
||||
|
||||
use Magento\Backend\App\Action;
|
||||
use Magento\Backend\App\Action\Context;
|
||||
use Magento\Framework\Controller\Result\JsonFactory;
|
||||
use Shopkeeper\VendorSalesReport\Model\ReportGenerator;
|
||||
use Shopkeeper\VendorSalesReport\Helper\Data as ConfigHelper;
|
||||
|
||||
class Grid extends Action
|
||||
{
|
||||
const ADMIN_RESOURCE = 'Shopkeeper_VendorSalesReport::report';
|
||||
|
||||
/**
|
||||
* @var JsonFactory
|
||||
*/
|
||||
protected $resultJsonFactory;
|
||||
|
||||
/**
|
||||
* @var ReportGenerator
|
||||
*/
|
||||
protected $reportGenerator;
|
||||
|
||||
/**
|
||||
* @var ConfigHelper
|
||||
*/
|
||||
protected $configHelper;
|
||||
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param JsonFactory $resultJsonFactory
|
||||
* @param ReportGenerator $reportGenerator
|
||||
* @param ConfigHelper $configHelper
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
JsonFactory $resultJsonFactory,
|
||||
ReportGenerator $reportGenerator,
|
||||
ConfigHelper $configHelper
|
||||
) {
|
||||
parent::__construct($context);
|
||||
$this->resultJsonFactory = $resultJsonFactory;
|
||||
$this->reportGenerator = $reportGenerator;
|
||||
$this->configHelper = $configHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid data action
|
||||
*
|
||||
* @return \Magento\Framework\Controller\Result\Json
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$result = $this->resultJsonFactory->create();
|
||||
|
||||
try {
|
||||
$startDate = $this->getRequest()->getParam('start_date');
|
||||
$endDate = $this->getRequest()->getParam('end_date');
|
||||
|
||||
if (!$startDate || !$endDate) {
|
||||
return $result->setData([
|
||||
'success' => false,
|
||||
'message' => 'Please select a date range.'
|
||||
]);
|
||||
}
|
||||
|
||||
// Get order statuses
|
||||
$orderStatuses = $this->configHelper->getOrderStatus();
|
||||
|
||||
// Generate report data
|
||||
$data = $this->reportGenerator->generateReportData($startDate, $endDate, $orderStatuses);
|
||||
|
||||
return $result->setData([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'total' => count($data)
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $result->setData([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Controller/Adminhtml/Report/Index.php
Normal file
43
Controller/Adminhtml/Report/Index.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Controller\Adminhtml\Report;
|
||||
|
||||
use Magento\Backend\App\Action;
|
||||
use Magento\Backend\App\Action\Context;
|
||||
use Magento\Framework\View\Result\PageFactory;
|
||||
|
||||
class Index extends Action
|
||||
{
|
||||
const ADMIN_RESOURCE = 'Shopkeeper_VendorSalesReport::report';
|
||||
|
||||
/**
|
||||
* @var PageFactory
|
||||
*/
|
||||
protected $resultPageFactory;
|
||||
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param PageFactory $resultPageFactory
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
PageFactory $resultPageFactory
|
||||
) {
|
||||
parent::__construct($context);
|
||||
$this->resultPageFactory = $resultPageFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index action
|
||||
*
|
||||
* @return \Magento\Backend\Model\View\Result\Page
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
|
||||
$resultPage = $this->resultPageFactory->create();
|
||||
$resultPage->setActiveMenu('Shopkeeper_VendorSalesReport::report');
|
||||
$resultPage->getConfig()->getTitle()->prepend(__('Vendor Sales Report'));
|
||||
|
||||
return $resultPage;
|
||||
}
|
||||
}
|
||||
99
Cron/GenerateMonthlyReport.php
Normal file
99
Cron/GenerateMonthlyReport.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Cron;
|
||||
|
||||
use Shopkeeper\VendorSalesReport\Helper\Data as ConfigHelper;
|
||||
use Shopkeeper\VendorSalesReport\Model\ReportGenerator;
|
||||
use Shopkeeper\VendorSalesReport\Model\EmailSender;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class GenerateMonthlyReport
|
||||
{
|
||||
/**
|
||||
* @var ConfigHelper
|
||||
*/
|
||||
protected $configHelper;
|
||||
|
||||
/**
|
||||
* @var ReportGenerator
|
||||
*/
|
||||
protected $reportGenerator;
|
||||
|
||||
/**
|
||||
* @var EmailSender
|
||||
*/
|
||||
protected $emailSender;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param ConfigHelper $configHelper
|
||||
* @param ReportGenerator $reportGenerator
|
||||
* @param EmailSender $emailSender
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigHelper $configHelper,
|
||||
ReportGenerator $reportGenerator,
|
||||
EmailSender $emailSender,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->configHelper = $configHelper;
|
||||
$this->reportGenerator = $reportGenerator;
|
||||
$this->emailSender = $emailSender;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute cron job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if (!$this->configHelper->isEnabled()) {
|
||||
$this->logger->info('Vendor Sales Report: Automated report is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get last month's date range
|
||||
$startDate = date('Y-m-01', strtotime('first day of last month'));
|
||||
$endDate = date('Y-m-t', strtotime('last day of last month'));
|
||||
|
||||
$month = date('F', strtotime('first day of last month'));
|
||||
$year = date('Y', strtotime('first day of last month'));
|
||||
|
||||
$this->logger->info("Vendor Sales Report: Generating report for {$month} {$year}");
|
||||
|
||||
// Get order statuses
|
||||
$orderStatuses = $this->configHelper->getOrderStatus();
|
||||
|
||||
// Generate report data
|
||||
$data = $this->reportGenerator->generateReportData($startDate, $endDate, $orderStatuses);
|
||||
|
||||
if (empty($data)) {
|
||||
$this->logger->info("Vendor Sales Report: No data found for {$month} {$year}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate CSV
|
||||
$csvContent = $this->reportGenerator->generateCsv($data);
|
||||
$filename = $this->reportGenerator->getFilename();
|
||||
|
||||
// Send email
|
||||
$emailSent = $this->emailSender->sendReport($csvContent, $filename, $month, $year);
|
||||
|
||||
if ($emailSent) {
|
||||
$this->logger->info("Vendor Sales Report: Successfully sent report for {$month} {$year}");
|
||||
} else {
|
||||
$this->logger->warning("Vendor Sales Report: Failed to send email for {$month} {$year}");
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Vendor Sales Report Cron Error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Helper/Data.php
Normal file
114
Helper/Data.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Helper;
|
||||
|
||||
use Magento\Framework\App\Helper\AbstractHelper;
|
||||
use Magento\Framework\App\Helper\Context;
|
||||
use Magento\Store\Model\ScopeInterface;
|
||||
|
||||
class Data extends AbstractHelper
|
||||
{
|
||||
const XML_PATH_ENABLED = 'vendorsalesreport/general/enabled';
|
||||
const XML_PATH_ORDER_STATUS = 'vendorsalesreport/general/order_status';
|
||||
const XML_PATH_EMAIL_ENABLED = 'vendorsalesreport/email/enabled';
|
||||
const XML_PATH_EMAIL_RECIPIENTS = 'vendorsalesreport/email/recipients';
|
||||
const XML_PATH_EMAIL_SUBJECT = 'vendorsalesreport/email/subject';
|
||||
const XML_PATH_EMAIL_SENDER = 'vendorsalesreport/email/sender';
|
||||
|
||||
/**
|
||||
* Check if automated report is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->scopeConfig->isSetFlag(
|
||||
self::XML_PATH_ENABLED,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order status filter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOrderStatus()
|
||||
{
|
||||
$value = $this->scopeConfig->getValue(
|
||||
self::XML_PATH_ORDER_STATUS,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
);
|
||||
|
||||
if (is_string($value)) {
|
||||
return explode(',', $value);
|
||||
}
|
||||
|
||||
return is_array($value) ? $value : ['complete'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailEnabled()
|
||||
{
|
||||
return $this->scopeConfig->isSetFlag(
|
||||
self::XML_PATH_EMAIL_ENABLED,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email recipients
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEmailRecipients()
|
||||
{
|
||||
$recipients = $this->scopeConfig->getValue(
|
||||
self::XML_PATH_EMAIL_RECIPIENTS,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
);
|
||||
|
||||
if (empty($recipients)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map('trim', explode(',', $recipients));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email subject
|
||||
*
|
||||
* @param string $month
|
||||
* @param string $year
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailSubject($month = null, $year = null)
|
||||
{
|
||||
$subject = $this->scopeConfig->getValue(
|
||||
self::XML_PATH_EMAIL_SUBJECT,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
);
|
||||
|
||||
if ($month && $year) {
|
||||
$subject = str_replace(['{{month}}', '{{year}}'], [$month, $year], $subject);
|
||||
}
|
||||
|
||||
return $subject ?: 'Monthly Thermo Report';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email sender
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailSender()
|
||||
{
|
||||
return $this->scopeConfig->getValue(
|
||||
self::XML_PATH_EMAIL_SENDER,
|
||||
ScopeInterface::SCOPE_STORE
|
||||
) ?: 'general';
|
||||
}
|
||||
}
|
||||
97
INSTALL.md
Normal file
97
INSTALL.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Quick Installation Guide
|
||||
|
||||
## Step 1: Upload Module Files
|
||||
|
||||
From your Magento root directory:
|
||||
|
||||
```bash
|
||||
# Create directory structure
|
||||
mkdir -p app/code/Shopkeeper/VendorSalesReport
|
||||
|
||||
# Upload all module files to this directory
|
||||
# (You can use FTP, SCP, or extract from ZIP)
|
||||
```
|
||||
|
||||
## Step 2: Enable Module
|
||||
|
||||
```bash
|
||||
php bin/magento module:enable Shopkeeper_VendorSalesReport
|
||||
php bin/magento setup:upgrade
|
||||
php bin/magento setup:di:compile
|
||||
php bin/magento setup:static-content:deploy -f
|
||||
php bin/magento cache:flush
|
||||
```
|
||||
|
||||
## Step 3: Configure Module
|
||||
|
||||
1. Log into Magento Admin
|
||||
2. Go to **Stores > Configuration > Shopkeeper > Vendor Sales Report**
|
||||
3. Configure settings:
|
||||
|
||||
### Required Settings:
|
||||
- **Enable Automated Monthly Report**: Yes
|
||||
- **Order Status Filter**: complete (or your preferred status)
|
||||
- **Enable Email**: Yes
|
||||
- **Email Recipients**: `vendor@example.com` (replace with actual email)
|
||||
- **Email Subject**: `Monthly Thermo Report - {{month}} {{year}}`
|
||||
- **Email Sender**: General Contact
|
||||
|
||||
4. Click **Save Config**
|
||||
5. Run `php bin/magento cache:flush`
|
||||
|
||||
## Step 4: Test Manual Report
|
||||
|
||||
1. Go to **Reports > Sales > Vendor Sales Report**
|
||||
2. Select a date range (defaults to last month)
|
||||
3. Click **Preview Report** to see data
|
||||
4. Click **Export CSV** to download
|
||||
|
||||
## Step 5: Verify Cron Setup
|
||||
|
||||
Test the automated report:
|
||||
|
||||
```bash
|
||||
php bin/magento cron:run --group default
|
||||
```
|
||||
|
||||
Check logs:
|
||||
```bash
|
||||
tail -f var/log/system.log | grep "Vendor Sales Report"
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Module not appearing in admin menu
|
||||
```bash
|
||||
php bin/magento cache:flush
|
||||
# Log out and log back into admin
|
||||
```
|
||||
|
||||
### CSV downloads as blank
|
||||
- Check that orders exist with the configured status in the date range
|
||||
- Verify order status configuration
|
||||
|
||||
### Emails not sending
|
||||
- Test Magento email with: System > Tools > Email Templates
|
||||
- Check SMTP settings
|
||||
- Verify email recipients are configured
|
||||
|
||||
## File Permissions (if needed)
|
||||
|
||||
```bash
|
||||
chmod -R 755 app/code/Shopkeeper/
|
||||
chown -R www-data:www-data app/code/Shopkeeper/ # adjust user as needed
|
||||
```
|
||||
|
||||
## Uninstallation (if needed)
|
||||
|
||||
```bash
|
||||
php bin/magento module:disable Shopkeeper_VendorSalesReport
|
||||
php bin/magento setup:upgrade
|
||||
rm -rf app/code/Shopkeeper/VendorSalesReport
|
||||
php bin/magento cache:flush
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
Check README.md for detailed documentation and troubleshooting.
|
||||
116
Model/EmailSender.php
Normal file
116
Model/EmailSender.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Model;
|
||||
|
||||
use Magento\Framework\Mail\Template\TransportBuilder;
|
||||
use Magento\Framework\Translate\Inline\StateInterface;
|
||||
use Magento\Store\Model\StoreManagerInterface;
|
||||
use Shopkeeper\VendorSalesReport\Helper\Data as ConfigHelper;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class EmailSender
|
||||
{
|
||||
/**
|
||||
* @var TransportBuilder
|
||||
*/
|
||||
protected $transportBuilder;
|
||||
|
||||
/**
|
||||
* @var StateInterface
|
||||
*/
|
||||
protected $inlineTranslation;
|
||||
|
||||
/**
|
||||
* @var StoreManagerInterface
|
||||
*/
|
||||
protected $storeManager;
|
||||
|
||||
/**
|
||||
* @var ConfigHelper
|
||||
*/
|
||||
protected $configHelper;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param TransportBuilder $transportBuilder
|
||||
* @param StateInterface $inlineTranslation
|
||||
* @param StoreManagerInterface $storeManager
|
||||
* @param ConfigHelper $configHelper
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
TransportBuilder $transportBuilder,
|
||||
StateInterface $inlineTranslation,
|
||||
StoreManagerInterface $storeManager,
|
||||
ConfigHelper $configHelper,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->transportBuilder = $transportBuilder;
|
||||
$this->inlineTranslation = $inlineTranslation;
|
||||
$this->storeManager = $storeManager;
|
||||
$this->configHelper = $configHelper;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send report email
|
||||
*
|
||||
* @param string $csvContent
|
||||
* @param string $filename
|
||||
* @param string $month
|
||||
* @param string $year
|
||||
* @return bool
|
||||
*/
|
||||
public function sendReport($csvContent, $filename, $month = null, $year = null)
|
||||
{
|
||||
if (!$this->configHelper->isEmailEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$recipients = $this->configHelper->getEmailRecipients();
|
||||
if (empty($recipients)) {
|
||||
$this->logger->warning('Vendor Sales Report: No email recipients configured');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->inlineTranslation->suspend();
|
||||
|
||||
$sender = [
|
||||
'name' => $this->storeManager->getStore()->getName(),
|
||||
'email' => $this->storeManager->getStore()->getConfig('trans_email/' . $this->configHelper->getEmailSender() . '/email')
|
||||
];
|
||||
|
||||
$subject = $this->configHelper->getEmailSubject($month, $year);
|
||||
|
||||
$transport = $this->transportBuilder
|
||||
->setTemplateIdentifier('vendorsalesreport_email_template')
|
||||
->setTemplateOptions([
|
||||
'area' => \Magento\Framework\App\Area::AREA_ADMINHTML,
|
||||
'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
|
||||
])
|
||||
->setTemplateVars([
|
||||
'subject' => $subject,
|
||||
'month' => $month,
|
||||
'year' => $year
|
||||
])
|
||||
->setFromByScope($sender)
|
||||
->addTo($recipients)
|
||||
->addAttachment($csvContent, $filename, 'text/csv')
|
||||
->getTransport();
|
||||
|
||||
$transport->sendMessage();
|
||||
|
||||
$this->inlineTranslation->resume();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Vendor Sales Report Email Error: ' . $e->getMessage());
|
||||
$this->inlineTranslation->resume();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
201
Model/ReportGenerator.php
Normal file
201
Model/ReportGenerator.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
namespace Shopkeeper\VendorSalesReport\Model;
|
||||
|
||||
use Magento\Framework\App\ResourceConnection;
|
||||
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
|
||||
|
||||
class ReportGenerator
|
||||
{
|
||||
/**
|
||||
* @var ResourceConnection
|
||||
*/
|
||||
protected $resourceConnection;
|
||||
|
||||
/**
|
||||
* @var TimezoneInterface
|
||||
*/
|
||||
protected $timezone;
|
||||
|
||||
/**
|
||||
* State abbreviation mapping
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $stateAbbreviations = [
|
||||
'Alabama' => 'AL', 'Alaska' => 'AK', 'Arizona' => 'AZ', 'Arkansas' => 'AR',
|
||||
'California' => 'CA', 'Colorado' => 'CO', 'Connecticut' => 'CT', 'Delaware' => 'DE',
|
||||
'Florida' => 'FL', 'Georgia' => 'GA', 'Hawaii' => 'HI', 'Idaho' => 'ID',
|
||||
'Illinois' => 'IL', 'Indiana' => 'IN', 'Iowa' => 'IA', 'Kansas' => 'KS',
|
||||
'Kentucky' => 'KY', 'Louisiana' => 'LA', 'Maine' => 'ME', 'Maryland' => 'MD',
|
||||
'Massachusetts' => 'MA', 'Michigan' => 'MI', 'Minnesota' => 'MN', 'Mississippi' => 'MS',
|
||||
'Missouri' => 'MO', 'Montana' => 'MT', 'Nebraska' => 'NE', 'Nevada' => 'NV',
|
||||
'New Hampshire' => 'NH', 'New Jersey' => 'NJ', 'New Mexico' => 'NM', 'New York' => 'NY',
|
||||
'North Carolina' => 'NC', 'North Dakota' => 'ND', 'Ohio' => 'OH', 'Oklahoma' => 'OK',
|
||||
'Oregon' => 'OR', 'Pennsylvania' => 'PA', 'Rhode Island' => 'RI', 'South Carolina' => 'SC',
|
||||
'South Dakota' => 'SD', 'Tennessee' => 'TN', 'Texas' => 'TX', 'Utah' => 'UT',
|
||||
'Vermont' => 'VT', 'Virginia' => 'VA', 'Washington' => 'WA', 'West Virginia' => 'WV',
|
||||
'Wisconsin' => 'WI', 'Wyoming' => 'WY', 'District of Columbia' => 'DC',
|
||||
'Puerto Rico' => 'PR', 'Guam' => 'GU', 'Virgin Islands' => 'VI'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ResourceConnection $resourceConnection
|
||||
* @param TimezoneInterface $timezone
|
||||
*/
|
||||
public function __construct(
|
||||
ResourceConnection $resourceConnection,
|
||||
TimezoneInterface $timezone
|
||||
) {
|
||||
$this->resourceConnection = $resourceConnection;
|
||||
$this->timezone = $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate report data
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param string $endDate
|
||||
* @param array $orderStatuses
|
||||
* @return array
|
||||
*/
|
||||
public function generateReportData($startDate, $endDate, $orderStatuses = ['complete'])
|
||||
{
|
||||
$connection = $this->resourceConnection->getConnection();
|
||||
|
||||
$salesOrderItemTable = $this->resourceConnection->getTableName('sales_order_item');
|
||||
$salesOrderTable = $this->resourceConnection->getTableName('sales_order');
|
||||
$salesOrderAddressTable = $this->resourceConnection->getTableName('sales_order_address');
|
||||
$catalogProductEntityTable = $this->resourceConnection->getTableName('catalog_product_entity');
|
||||
$catalogProductEntityDecimalTable = $this->resourceConnection->getTableName('catalog_product_entity_decimal');
|
||||
$eavAttributeTable = $this->resourceConnection->getTableName('eav_attribute');
|
||||
|
||||
// Build status filter
|
||||
$statusPlaceholders = implode(',', array_fill(0, count($orderStatuses), '?'));
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
DATE_FORMAT(o.created_at, '%m/%d/%Y') AS 'Date',
|
||||
oi.sku AS 'Vendor Product number',
|
||||
oi.name AS 'Description',
|
||||
'' AS 'Vendor code',
|
||||
'' AS 'Vendor name',
|
||||
o.increment_id AS 'Dealer product number',
|
||||
CASE
|
||||
WHEN sa.country_id = 'US' THEN 'United States'
|
||||
ELSE sa.country_id
|
||||
END AS 'Country',
|
||||
sa.city AS 'City',
|
||||
sa.region AS 'State',
|
||||
sa.postcode AS 'Zip code',
|
||||
CAST(oi.qty_ordered AS UNSIGNED) AS 'Quantity',
|
||||
COALESCE(
|
||||
NULLIF(oi.base_cost, 0),
|
||||
current_cost.value,
|
||||
0
|
||||
) AS 'Cost',
|
||||
o.status AS 'Order Status'
|
||||
FROM
|
||||
{$salesOrderItemTable} oi
|
||||
INNER JOIN
|
||||
{$salesOrderTable} o ON oi.order_id = o.entity_id
|
||||
INNER JOIN
|
||||
{$salesOrderAddressTable} sa ON o.entity_id = sa.parent_id
|
||||
AND sa.address_type = 'shipping'
|
||||
LEFT JOIN
|
||||
{$catalogProductEntityTable} cpe ON oi.sku = cpe.sku
|
||||
LEFT JOIN
|
||||
{$eavAttributeTable} ea ON ea.attribute_code = 'cost'
|
||||
AND ea.entity_type_id = 4
|
||||
LEFT JOIN
|
||||
{$catalogProductEntityDecimalTable} current_cost ON current_cost.entity_id = cpe.entity_id
|
||||
AND current_cost.attribute_id = ea.attribute_id
|
||||
AND current_cost.store_id = 0
|
||||
WHERE
|
||||
o.created_at BETWEEN ? AND ?
|
||||
AND o.status IN ({$statusPlaceholders})
|
||||
AND oi.product_type != 'configurable'
|
||||
ORDER BY
|
||||
o.created_at DESC, o.entity_id, oi.item_id
|
||||
";
|
||||
|
||||
$bind = array_merge(
|
||||
[$startDate . ' 00:00:00', $endDate . ' 23:59:59'],
|
||||
$orderStatuses
|
||||
);
|
||||
|
||||
$results = $connection->fetchAll($sql, $bind);
|
||||
|
||||
// Convert state names to abbreviations
|
||||
foreach ($results as &$row) {
|
||||
$row['State'] = $this->abbreviateState($row['State']);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviate state name
|
||||
*
|
||||
* @param string $stateName
|
||||
* @return string
|
||||
*/
|
||||
protected function abbreviateState($stateName)
|
||||
{
|
||||
// If already abbreviated (2 characters), return as-is
|
||||
if (strlen($stateName) == 2) {
|
||||
return $stateName;
|
||||
}
|
||||
|
||||
// Look up abbreviation
|
||||
if (isset($this->stateAbbreviations[$stateName])) {
|
||||
return $this->stateAbbreviations[$stateName];
|
||||
}
|
||||
|
||||
// Return original if not found
|
||||
return $stateName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSV content
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function generateCsv($data)
|
||||
{
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = fopen('php://temp', 'r+');
|
||||
|
||||
// Headers - exclude Order Status
|
||||
$headers = array_keys($data[0]);
|
||||
$headers = array_filter($headers, function($header) {
|
||||
return $header !== 'Order Status';
|
||||
});
|
||||
fputcsv($output, $headers);
|
||||
|
||||
// Data rows - exclude Order Status
|
||||
foreach ($data as $row) {
|
||||
unset($row['Order Status']);
|
||||
fputcsv($output, array_values($row));
|
||||
}
|
||||
|
||||
rewind($output);
|
||||
$csv = stream_get_contents($output);
|
||||
fclose($output);
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename()
|
||||
{
|
||||
return 'FF_SAC_730_MCQUEEN.csv';
|
||||
}
|
||||
}
|
||||
254
README.md
Normal file
254
README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Shopkeeper Vendor Sales Report
|
||||
|
||||
A Magento 2.4.x module that generates monthly vendor sales reports with automated email delivery.
|
||||
|
||||
## Features
|
||||
|
||||
- **Manual Report Generation**: Generate reports for any custom date range via admin interface
|
||||
- **Preview Grid**: View report data before exporting
|
||||
- **Automated Monthly Reports**: Automatic generation on the 1st of each month via cron
|
||||
- **Email Delivery**: Automatically email reports with CSV attachment
|
||||
- **Configurable**:
|
||||
- Order status filtering
|
||||
- Email recipients
|
||||
- Email subject line
|
||||
- Email sender
|
||||
- **State Abbreviation**: Automatically converts full state names to 2-letter codes
|
||||
- **Cost Fallback**: Uses current product cost if order item cost is missing
|
||||
|
||||
## Installation
|
||||
|
||||
### Method 1: Manual Installation
|
||||
|
||||
1. **Upload the module files:**
|
||||
```bash
|
||||
# From your Magento root directory
|
||||
mkdir -p app/code/Shopkeeper/VendorSalesReport
|
||||
# Upload all module files to app/code/Shopkeeper/VendorSalesReport/
|
||||
```
|
||||
|
||||
2. **Enable the module:**
|
||||
```bash
|
||||
php bin/magento module:enable Shopkeeper_VendorSalesReport
|
||||
php bin/magento setup:upgrade
|
||||
php bin/magento setup:di:compile
|
||||
php bin/magento setup:static-content:deploy -f
|
||||
php bin/magento cache:flush
|
||||
```
|
||||
|
||||
3. **Set permissions (if needed):**
|
||||
```bash
|
||||
chmod -R 755 app/code/Shopkeeper/
|
||||
```
|
||||
|
||||
### Method 2: Composer Installation (if you package it)
|
||||
|
||||
```bash
|
||||
composer require shopkeeper/module-vendor-sales-report
|
||||
php bin/magento module:enable Shopkeeper_VendorSalesReport
|
||||
php bin/magento setup:upgrade
|
||||
php bin/magento setup:di:compile
|
||||
php bin/magento setup:static-content:deploy -f
|
||||
php bin/magento cache:flush
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Admin Configuration Path
|
||||
**Stores > Configuration > Shopkeeper > Vendor Sales Report**
|
||||
|
||||
### Settings
|
||||
|
||||
#### General Settings
|
||||
- **Enable Automated Monthly Report**: Enable/disable automatic monthly report generation
|
||||
- **Order Status Filter**: Select which order statuses to include (default: Complete)
|
||||
|
||||
#### Email Settings
|
||||
- **Enable Email**: Enable/disable email delivery
|
||||
- **Email Recipients**: Comma-separated list of email addresses
|
||||
- **Email Subject**: Subject line for automated emails (supports {{month}} and {{year}} placeholders)
|
||||
- **Email Sender**: Choose which store email identity to use as sender
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```
|
||||
Enable Automated Monthly Report: Yes
|
||||
Order Status Filter: complete
|
||||
Enable Email: Yes
|
||||
Email Recipients: vendor@example.com, admin@example.com
|
||||
Email Subject: Monthly Thermo Report - {{month}} {{year}}
|
||||
Email Sender: General Contact
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Manual Report Generation
|
||||
|
||||
1. Go to **Reports > Sales > Vendor Sales Report**
|
||||
2. Select date range using the date pickers
|
||||
3. Click **Preview Report** to view data in a grid
|
||||
4. Click **Export CSV** to download the report
|
||||
|
||||
The report will be downloaded as `FF_SAC_730_MCQUEEN.csv`
|
||||
|
||||
### Automated Monthly Reports
|
||||
|
||||
Once configured, the module will automatically:
|
||||
1. Generate a report on the 1st of each month at 2:00 AM
|
||||
2. Include all orders from the previous month
|
||||
3. Email the report as a CSV attachment to configured recipients
|
||||
|
||||
### Report Format
|
||||
|
||||
The CSV file includes the following columns:
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Date | Order date (MM/DD/YYYY format) |
|
||||
| Vendor Product number | Product SKU |
|
||||
| Description | Product name |
|
||||
| Vendor code | (Empty - reserved for vendor use) |
|
||||
| Vendor name | (Empty - reserved for vendor use) |
|
||||
| Dealer product number | Order increment ID |
|
||||
| Country | Shipping address country |
|
||||
| City | Shipping address city |
|
||||
| State | Shipping address state (abbreviated) |
|
||||
| Zip code | Shipping address postal code |
|
||||
| Quantity | Quantity ordered |
|
||||
| Cost | Product cost (uses order item cost, falls back to current product cost if missing) |
|
||||
|
||||
## Cron Schedule
|
||||
|
||||
The automated report runs according to this schedule:
|
||||
```
|
||||
0 2 1 * *
|
||||
```
|
||||
Translation: At 2:00 AM on the 1st day of every month
|
||||
|
||||
To test the cron manually:
|
||||
```bash
|
||||
php bin/magento cron:run --group default
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Reports not generating automatically
|
||||
|
||||
1. **Check if cron is enabled:**
|
||||
```bash
|
||||
php bin/magento cron:run
|
||||
```
|
||||
|
||||
2. **Check cron_schedule table:**
|
||||
```sql
|
||||
SELECT * FROM cron_schedule
|
||||
WHERE job_code = 'shopkeeper_vendorsalesreport_monthly'
|
||||
ORDER BY scheduled_at DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
3. **Check module configuration:**
|
||||
- Ensure "Enable Automated Monthly Report" is set to Yes
|
||||
- Verify order status is configured
|
||||
|
||||
### Emails not sending
|
||||
|
||||
1. **Check email configuration:**
|
||||
- Verify email recipients are configured
|
||||
- Ensure "Enable Email" is set to Yes
|
||||
- Check Magento's email sending is working (test with other emails)
|
||||
|
||||
2. **Check logs:**
|
||||
```bash
|
||||
tail -f var/log/system.log | grep "Vendor Sales Report"
|
||||
```
|
||||
|
||||
### No data in report
|
||||
|
||||
1. **Check order status filter:**
|
||||
- Verify orders have the configured status (default: "complete")
|
||||
- Try including additional statuses like "processing"
|
||||
|
||||
2. **Verify date range:**
|
||||
- Ensure orders exist within the selected date range
|
||||
|
||||
### State abbreviations not working
|
||||
|
||||
- The module includes all US states, DC, and territories
|
||||
- If international addresses appear, they'll show as-is (not abbreviated)
|
||||
- Already abbreviated states (2 characters) are left unchanged
|
||||
|
||||
## Module Structure
|
||||
|
||||
```
|
||||
Shopkeeper/VendorSalesReport/
|
||||
├── Block/
|
||||
│ └── Adminhtml/
|
||||
│ └── Report.php
|
||||
├── Controller/
|
||||
│ └── Adminhtml/
|
||||
│ └── Report/
|
||||
│ ├── Export.php
|
||||
│ ├── Grid.php
|
||||
│ └── Index.php
|
||||
├── Cron/
|
||||
│ └── GenerateMonthlyReport.php
|
||||
├── etc/
|
||||
│ ├── acl.xml
|
||||
│ ├── config.xml
|
||||
│ ├── crontab.xml
|
||||
│ ├── email_templates.xml
|
||||
│ ├── module.xml
|
||||
│ └── adminhtml/
|
||||
│ ├── menu.xml
|
||||
│ ├── routes.xml
|
||||
│ └── system.xml
|
||||
├── Helper/
|
||||
│ └── Data.php
|
||||
├── Model/
|
||||
│ ├── EmailSender.php
|
||||
│ └── ReportGenerator.php
|
||||
├── view/
|
||||
│ └── adminhtml/
|
||||
│ ├── email/
|
||||
│ │ └── report.html
|
||||
│ ├── layout/
|
||||
│ │ └── vendorsalesreport_report_index.xml
|
||||
│ └── templates/
|
||||
│ └── report/
|
||||
│ └── view.phtml
|
||||
└── registration.php
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Magento 2.4.x
|
||||
- PHP 7.4 or higher
|
||||
- MySQL 5.7 or higher
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the troubleshooting section above
|
||||
2. Review Magento logs: `var/log/system.log`
|
||||
3. Verify module is enabled: `php bin/magento module:status`
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - Shopkeeper
|
||||
|
||||
## Version
|
||||
|
||||
1.0.0
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0.0
|
||||
- Initial release
|
||||
- Manual report generation with date range selector
|
||||
- Preview grid functionality
|
||||
- Automated monthly cron job
|
||||
- Email delivery with CSV attachment
|
||||
- Configurable order status filter
|
||||
- State abbreviation support
|
||||
- Cost fallback to current product cost
|
||||
28
composer.json
Normal file
28
composer.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "shopkeeper/module-vendor-sales-report",
|
||||
"description": "Automated vendor sales report generator with email delivery for Magento 2",
|
||||
"type": "magento2-module",
|
||||
"version": "1.0.0",
|
||||
"license": "proprietary",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Shopkeeper",
|
||||
"email": "taber@shopkeeper.dev"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4|^8.0|^8.1|^8.2|^8.3",
|
||||
"magento/framework": "^103.0|^104.0",
|
||||
"magento/module-backend": "^102.0|^103.0",
|
||||
"magento/module-sales": "^103.0|^104.0",
|
||||
"magento/module-cron": "^100.4|^101.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"registration.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Shopkeeper\\VendorSalesReport\\": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
21
etc/acl.xml
Normal file
21
etc/acl.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
|
||||
<acl>
|
||||
<resources>
|
||||
<resource id="Magento_Backend::admin">
|
||||
<resource id="Magento_Reports::report">
|
||||
<resource id="Magento_Reports::report_salesroot">
|
||||
<resource id="Shopkeeper_VendorSalesReport::report" title="Vendor Sales Report" sortOrder="999"/>
|
||||
</resource>
|
||||
</resource>
|
||||
<resource id="Magento_Backend::stores">
|
||||
<resource id="Magento_Backend::stores_settings">
|
||||
<resource id="Magento_Config::config">
|
||||
<resource id="Shopkeeper_VendorSalesReport::config" title="Vendor Sales Report"/>
|
||||
</resource>
|
||||
</resource>
|
||||
</resource>
|
||||
</resource>
|
||||
</resources>
|
||||
</acl>
|
||||
</config>
|
||||
12
etc/adminhtml/menu.xml
Normal file
12
etc/adminhtml/menu.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
|
||||
<menu>
|
||||
<add id="Shopkeeper_VendorSalesReport::report"
|
||||
title="Vendor Sales Report"
|
||||
module="Shopkeeper_VendorSalesReport"
|
||||
sortOrder="999"
|
||||
parent="Magento_Reports::report_salesroot"
|
||||
action="vendorsalesreport/report/index"
|
||||
resource="Shopkeeper_VendorSalesReport::report"/>
|
||||
</menu>
|
||||
</config>
|
||||
8
etc/adminhtml/routes.xml
Normal file
8
etc/adminhtml/routes.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
|
||||
<router id="admin">
|
||||
<route id="vendorsalesreport" frontName="vendorsalesreport">
|
||||
<module name="Shopkeeper_VendorSalesReport"/>
|
||||
</route>
|
||||
</router>
|
||||
</config>
|
||||
55
etc/adminhtml/system.xml
Normal file
55
etc/adminhtml/system.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
|
||||
<system>
|
||||
<tab id="shopkeeper" translate="label" sortOrder="300">
|
||||
<label>Shopkeeper</label>
|
||||
</tab>
|
||||
<section id="vendorsalesreport" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Vendor Sales Report</label>
|
||||
<tab>shopkeeper</tab>
|
||||
<resource>Shopkeeper_VendorSalesReport::config</resource>
|
||||
<group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>General Settings</label>
|
||||
<field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Enable Automated Monthly Report</label>
|
||||
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
|
||||
<comment>When enabled, report will be generated automatically on the 1st of each month</comment>
|
||||
</field>
|
||||
<field id="order_status" translate="label comment" type="multiselect" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Order Status Filter</label>
|
||||
<source_model>Magento\Sales\Model\Config\Source\Order\Status</source_model>
|
||||
<comment>Select which order statuses to include in the report</comment>
|
||||
</field>
|
||||
</group>
|
||||
<group id="email" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Email Settings</label>
|
||||
<field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Enable Email</label>
|
||||
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
|
||||
<comment>Send automated report via email</comment>
|
||||
</field>
|
||||
<field id="recipients" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Email Recipients</label>
|
||||
<comment>Comma-separated email addresses (e.g., vendor@example.com, admin@example.com)</comment>
|
||||
<depends>
|
||||
<field id="enabled">1</field>
|
||||
</depends>
|
||||
</field>
|
||||
<field id="subject" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Email Subject</label>
|
||||
<comment>Subject line for automated report emails. Use {{month}} and {{year}} for dynamic values.</comment>
|
||||
<depends>
|
||||
<field id="enabled">1</field>
|
||||
</depends>
|
||||
</field>
|
||||
<field id="sender" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0">
|
||||
<label>Email Sender</label>
|
||||
<source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model>
|
||||
<depends>
|
||||
<field id="enabled">1</field>
|
||||
</depends>
|
||||
</field>
|
||||
</group>
|
||||
</section>
|
||||
</system>
|
||||
</config>
|
||||
16
etc/config.xml
Normal file
16
etc/config.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
|
||||
<default>
|
||||
<vendorsalesreport>
|
||||
<general>
|
||||
<enabled>1</enabled>
|
||||
<order_status>complete</order_status>
|
||||
</general>
|
||||
<email>
|
||||
<enabled>1</enabled>
|
||||
<subject>Monthly Thermo Report - {{month}} {{year}}</subject>
|
||||
<sender>general</sender>
|
||||
</email>
|
||||
</vendorsalesreport>
|
||||
</default>
|
||||
</config>
|
||||
8
etc/crontab.xml
Normal file
8
etc/crontab.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
|
||||
<group id="default">
|
||||
<job name="shopkeeper_vendorsalesreport_monthly" instance="Shopkeeper\VendorSalesReport\Cron\GenerateMonthlyReport" method="execute">
|
||||
<schedule>0 2 1 * *</schedule>
|
||||
</job>
|
||||
</group>
|
||||
</config>
|
||||
4
etc/email_templates.xml
Normal file
4
etc/email_templates.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd">
|
||||
<template id="vendorsalesreport_email_template" label="Vendor Sales Report Email" file="report.html" type="html" module="Shopkeeper_VendorSalesReport" area="adminhtml"/>
|
||||
</config>
|
||||
10
etc/module.xml
Normal file
10
etc/module.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
|
||||
<module name="Shopkeeper_VendorSalesReport" setup_version="1.0.0">
|
||||
<sequence>
|
||||
<module name="Magento_Sales"/>
|
||||
<module name="Magento_Backend"/>
|
||||
<module name="Magento_Cron"/>
|
||||
</sequence>
|
||||
</module>
|
||||
</config>
|
||||
9
registration.php
Normal file
9
registration.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Shopkeeper VendorSalesReport Module Registration
|
||||
*/
|
||||
\Magento\Framework\Component\ComponentRegistrar::register(
|
||||
\Magento\Framework\Component\ComponentRegistrar::MODULE,
|
||||
'Shopkeeper_VendorSalesReport',
|
||||
__DIR__
|
||||
);
|
||||
22
view/adminhtml/email/report.html
Normal file
22
view/adminhtml/email/report.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--@subject {{var subject}} @-->
|
||||
<!--@vars
|
||||
{"var subject":"Email Subject"}
|
||||
@-->
|
||||
|
||||
{{template config_path="design/email/header_template"}}
|
||||
|
||||
<table>
|
||||
<tr class="email-intro">
|
||||
<td>
|
||||
<p class="greeting">Hello,</p>
|
||||
<p>
|
||||
Please find attached the monthly vendor sales report for {{var month}} {{var year}}.
|
||||
</p>
|
||||
<p>
|
||||
This report contains all completed orders for the specified period.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{{template config_path="design/email/footer_template"}}
|
||||
12
view/adminhtml/layout/vendorsalesreport_report_index.xml
Normal file
12
view/adminhtml/layout/vendorsalesreport_report_index.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
|
||||
<head>
|
||||
<css src="Shopkeeper_VendorSalesReport::css/vendor-report.css"/>
|
||||
</head>
|
||||
<update handle="styles"/>
|
||||
<body>
|
||||
<referenceContainer name="content">
|
||||
<block class="Shopkeeper\VendorSalesReport\Block\Adminhtml\Report" name="vendorsalesreport.report" template="Shopkeeper_VendorSalesReport::report/view.phtml"/>
|
||||
</referenceContainer>
|
||||
</body>
|
||||
</page>
|
||||
299
view/adminhtml/templates/report/view.phtml
Normal file
299
view/adminhtml/templates/report/view.phtml
Normal file
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
/**
|
||||
* @var $block \Shopkeeper\VendorSalesReport\Block\Adminhtml\Report
|
||||
*/
|
||||
?>
|
||||
<style>
|
||||
/* Fix icon display - more aggressive targeting */
|
||||
.item-vendorsalesreport > a:before,
|
||||
#menu-magento-reports-report .item-vendorsalesreport > a:before,
|
||||
a[href*="vendorsalesreport"]:before {
|
||||
content: '' !important;
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
/* Fix date range layout - align fields properly */
|
||||
.date-range-wrapper {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.date-range-wrapper .admin__field {
|
||||
flex: 0 0 250px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.date-range-wrapper .admin__field-label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.date-range-wrapper .field-date {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Report grid styling */
|
||||
#report_grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#report_grid th,
|
||||
#report_grid td {
|
||||
padding: 10px 8px;
|
||||
text-align: left;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#report_grid th {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
border-color: #666;
|
||||
}
|
||||
#report_grid tbody tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
#report_grid tbody tr:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.admin__page-section-title-note {
|
||||
margin-left: 10px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="admin__page-section">
|
||||
<div class="admin__page-section-title">
|
||||
<span class="title"><?= $block->escapeHtml(__('Vendor Sales Report Generator')) ?></span>
|
||||
</div>
|
||||
<div class="admin__page-section-content">
|
||||
<div class="admin__fieldset">
|
||||
<div class="admin__field">
|
||||
<div class="admin__field-label">
|
||||
<label><?= $block->escapeHtml(__('Date Range')) ?></label>
|
||||
</div>
|
||||
<div class="admin__field-control">
|
||||
<div class="date-range-wrapper">
|
||||
<div class="admin__field _required field-date">
|
||||
<div class="admin__field-label">
|
||||
<label>
|
||||
<span><?= $block->escapeHtml(__('From')) ?></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="admin__field-control">
|
||||
<input type="text"
|
||||
id="start_date"
|
||||
name="start_date"
|
||||
class="admin__control-text"
|
||||
value="<?= $block->escapeHtmlAttr($block->getDefaultStartDate()) ?>"
|
||||
data-mage-init='{"calendar": {"dateFormat": "yy-mm-dd"}}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin__field _required field-date">
|
||||
<div class="admin__field-label">
|
||||
<label>
|
||||
<span><?= $block->escapeHtml(__('To')) ?></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="admin__field-control">
|
||||
<input type="text"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
class="admin__control-text"
|
||||
value="<?= $block->escapeHtmlAttr($block->getDefaultEndDate()) ?>"
|
||||
data-mage-init='{"calendar": {"dateFormat": "yy-mm-dd"}}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin__field">
|
||||
<div class="admin__field-label">
|
||||
<label><?= $block->escapeHtml(__('Order Status Filter')) ?></label>
|
||||
</div>
|
||||
<div class="admin__field-control">
|
||||
<div class="admin__field-note">
|
||||
<span><?= $block->escapeHtml(__('Currently configured to include: %1', implode(', ', $block->getOrderStatuses()))) ?></span>
|
||||
<br/>
|
||||
<span><?= $block->escapeHtml(__('To change this, go to: Stores > Configuration > Shopkeeper > Vendor Sales Report')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin__field">
|
||||
<div class="admin__field-control">
|
||||
<button id="preview_report"
|
||||
type="button"
|
||||
class="action-default scalable primary"
|
||||
style="margin-right: 10px;">
|
||||
<span><?= $block->escapeHtml(__('Preview Report')) ?></span>
|
||||
</button>
|
||||
<button id="export_report"
|
||||
type="button"
|
||||
class="action-default scalable">
|
||||
<span><?= $block->escapeHtml(__('Export CSV')) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="report_preview" class="admin__page-section" style="display: none; margin-top: 20px;">
|
||||
<div class="admin__page-section-title">
|
||||
<span class="title"><?= $block->escapeHtml(__('Report Preview')) ?></span>
|
||||
<span id="record_count" class="admin__page-section-title-note"></span>
|
||||
</div>
|
||||
<div class="admin__page-section-content">
|
||||
<div class="admin__data-grid-wrap">
|
||||
<table class="data-grid" id="report_grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $block->escapeHtml(__('Date')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Vendor Product #')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Description')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Dealer Product #')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Country')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('City')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('State')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Zip Code')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Quantity')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Cost')) ?></th>
|
||||
<th><?= $block->escapeHtml(__('Order Status')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="report_tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/x-magento-init">
|
||||
{
|
||||
"*": {
|
||||
"Magento_Ui/js/core/app": {
|
||||
"components": {
|
||||
"vendorReportApp": {
|
||||
"component": "Shopkeeper_VendorSalesReport/js/report"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
require(['jquery', 'Magento_Ui/js/modal/alert', 'loader'], function($, alert) {
|
||||
$(document).ready(function() {
|
||||
var gridUrl = '<?= $block->escapeJs($block->escapeUrl($block->getGridUrl())) ?>';
|
||||
var exportUrl = '<?= $block->escapeJs($block->escapeUrl($block->getExportUrl())) ?>';
|
||||
|
||||
$('#preview_report').on('click', function() {
|
||||
var startDate = $('#start_date').val();
|
||||
var endDate = $('#end_date').val();
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
alert({
|
||||
content: '<?= $block->escapeJs(__('Please select a date range.')) ?>'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').trigger('processStart');
|
||||
|
||||
$.ajax({
|
||||
url: gridUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
form_key: FORM_KEY
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$('body').trigger('processStop');
|
||||
|
||||
if (response.success) {
|
||||
var tbody = $('#report_tbody');
|
||||
tbody.empty();
|
||||
|
||||
if (response.data.length === 0) {
|
||||
tbody.append('<tr><td colspan="11" style="text-align: center;"><?= $block->escapeJs(__('No data found for the selected date range.')) ?></td></tr>');
|
||||
$('#record_count').text('');
|
||||
} else {
|
||||
$.each(response.data, function(index, row) {
|
||||
var tr = $('<tr>');
|
||||
tr.append($('<td>').text(row.Date));
|
||||
tr.append($('<td>').text(row['Vendor Product number']));
|
||||
tr.append($('<td>').text(row.Description));
|
||||
tr.append($('<td>').text(row['Dealer product number']));
|
||||
tr.append($('<td>').text(row.Country));
|
||||
tr.append($('<td>').text(row.City));
|
||||
tr.append($('<td>').text(row.State));
|
||||
tr.append($('<td>').text(row['Zip code']));
|
||||
tr.append($('<td>').text(row.Quantity));
|
||||
tr.append($('<td>').text('$' + parseFloat(row.Cost).toFixed(2)));
|
||||
tr.append($('<td>').text(row['Order Status']));
|
||||
tbody.append(tr);
|
||||
});
|
||||
$('#record_count').text('(' + response.total + ' records)');
|
||||
}
|
||||
|
||||
$('#report_preview').show();
|
||||
} else {
|
||||
alert({
|
||||
content: response.message || '<?= $block->escapeJs(__('An error occurred while loading the report.')) ?>'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$('body').trigger('processStop');
|
||||
alert({
|
||||
content: '<?= $block->escapeJs(__('An error occurred while loading the report.')) ?>'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#export_report').on('click', function() {
|
||||
var startDate = $('#start_date').val();
|
||||
var endDate = $('#end_date').val();
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
alert({
|
||||
content: '<?= $block->escapeJs(__('Please select a date range.')) ?>'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var form = $('<form>', {
|
||||
'method': 'POST',
|
||||
'action': exportUrl
|
||||
});
|
||||
|
||||
form.append($('<input>', {
|
||||
'type': 'hidden',
|
||||
'name': 'start_date',
|
||||
'value': startDate
|
||||
}));
|
||||
|
||||
form.append($('<input>', {
|
||||
'type': 'hidden',
|
||||
'name': 'end_date',
|
||||
'value': endDate
|
||||
}));
|
||||
|
||||
form.append($('<input>', {
|
||||
'type': 'hidden',
|
||||
'name': 'form_key',
|
||||
'value': FORM_KEY
|
||||
}));
|
||||
|
||||
$('body').append(form);
|
||||
form.submit();
|
||||
form.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
15
view/adminhtml/web/css/vendor-report.css
Normal file
15
view/adminhtml/web/css/vendor-report.css
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Vendor Sales Report Menu - Remove icon */
|
||||
.item-vendorsalesreport > a:before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Additional targeting for stubborn icons */
|
||||
#menu-magento-reports-report .item-vendorsalesreport > a:before,
|
||||
.admin__menu .item-vendorsalesreport > a:before {
|
||||
content: none !important;
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user