mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-27 19:51:09 -04:00
Compare commits
25 Commits
e5afc93efa
...
mail-sende
| Author | SHA1 | Date | |
|---|---|---|---|
| dea73bcdf8 | |||
| aececb8575 | |||
| 2bea727d19 | |||
| aede1f76d0 | |||
| 959aa257b4 | |||
| b4aa254b68 | |||
| c1f2af5174 | |||
| 05d5ce26fd | |||
| 393fe20010 | |||
| 57bdbd2897 | |||
| 7447cc24f9 | |||
| 889d22d92c | |||
| bc8f2cd484 | |||
| 4e47f58bad | |||
| d8c429912e | |||
| 0aaf0e7e75 | |||
| 3d0b89bb4d | |||
| 38c4b9ebce | |||
| 7be59e78e0 | |||
| 204483836a | |||
| 33bc9ded65 | |||
| 4271ef451e | |||
| 6eb44fba93 | |||
| 96e7300583 | |||
| a479d966d1 |
161
.github/workflows/uffizzi-build.yml
vendored
Normal file
161
.github/workflows/uffizzi-build.yml
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
name: Build PR Image
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened,synchronize,reopened,closed]
|
||||
|
||||
jobs:
|
||||
|
||||
build-application:
|
||||
name: Build and Push `application`
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Generate UUID image name
|
||||
id: uuid
|
||||
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
|
||||
tags: type=raw,value=60d
|
||||
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
context: ./
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./uffizzi/Dockerfile
|
||||
|
||||
build-nginx:
|
||||
needs:
|
||||
- build-application
|
||||
name: Build and Push `nginx`
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Generate UUID image name
|
||||
id: uuid
|
||||
run: echo "UUID_TAG_NGINX=$(uuidgen)" >> $GITHUB_ENV
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_TAG_NGINX }}
|
||||
tags: type=raw,value=60d
|
||||
- name: Build and Push Image to Uffizzi ephemeral registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
context: ./
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./uffizzi/nginx/Dockerfile
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ needs.build-application.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
|
||||
build-crond:
|
||||
name: Build and Push `crond`
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Generate UUID image name
|
||||
id: uuid
|
||||
run: echo "UUID_TAG_CROND=$(uuidgen)" >> $GITHUB_ENV
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_TAG_CROND }}
|
||||
tags: type=raw,value=60d
|
||||
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
context: ./
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./uffizzi/crond/Dockerfile
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
|
||||
|
||||
render-compose-file:
|
||||
name: Render Docker Compose File
|
||||
# Pass output of this workflow to another triggered by `workflow_run` event.
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
|
||||
needs:
|
||||
- build-application
|
||||
- build-nginx
|
||||
- build-crond
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Render Compose File
|
||||
run: |
|
||||
APP_IMAGE=$(echo ${{ needs.build-application.outputs.tags }})
|
||||
export APP_IMAGE
|
||||
NGINX_IMAGE=$(echo ${{ needs.build-nginx.outputs.tags }})
|
||||
export NGINX_IMAGE
|
||||
CROND_IMAGE=$(echo ${{ needs.build-crond.outputs.tags }})
|
||||
export CROND_IMAGE
|
||||
# Render simple template from environment variables.
|
||||
envsubst < ./uffizzi/docker-compose.uffizzi.yml > docker-compose.rendered.yml
|
||||
cat docker-compose.rendered.yml
|
||||
- name: Upload Rendered Compose File as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: docker-compose.rendered.yml
|
||||
retention-days: 2
|
||||
- name: Serialize PR Event to File
|
||||
run: |
|
||||
cat << EOF > event.json
|
||||
${{ toJSON(github.event) }}
|
||||
|
||||
EOF
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
|
||||
delete-preview:
|
||||
name: Call for Preview Deletion
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action == 'closed' }}
|
||||
steps:
|
||||
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
|
||||
- name: Serialize PR Event to File
|
||||
run: echo '${{ toJSON(github.event) }}' > event.json
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
|
||||
84
.github/workflows/uffizzi-preview.yml
vendored
Normal file
84
.github/workflows/uffizzi-preview.yml
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
name: Deploy Uffizzi Preview
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "Build PR Image"
|
||||
types:
|
||||
- completed
|
||||
|
||||
|
||||
jobs:
|
||||
cache-compose-file:
|
||||
name: Cache Compose File
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
|
||||
pr-number: ${{ env.PR_NUMBER }}
|
||||
steps:
|
||||
- name: 'Download artifacts'
|
||||
# Fetch output (zip archive) from the workflow run that triggered this workflow.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
});
|
||||
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "preview-spec"
|
||||
})[0];
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
|
||||
- name: 'Unzip artifact'
|
||||
run: unzip preview-spec.zip
|
||||
- name: Read Event into ENV
|
||||
run: |
|
||||
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
|
||||
cat event.json >> $GITHUB_ENV
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
- name: Hash Rendered Compose File
|
||||
id: hash
|
||||
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV
|
||||
- name: Cache Rendered Compose File
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: docker-compose.rendered.yml
|
||||
key: ${{ env.COMPOSE_FILE_HASH }}
|
||||
|
||||
- name: Read PR Number From Event Object
|
||||
id: pr
|
||||
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: DEBUG - Print Job Outputs
|
||||
if: ${{ runner.debug }}
|
||||
run: |
|
||||
echo "PR number: ${{ env.PR_NUMBER }}"
|
||||
echo "Compose file hash: ${{ env.COMPOSE_FILE_HASH }}"
|
||||
cat event.json
|
||||
deploy-uffizzi-preview:
|
||||
name: Use Remote Workflow to Preview on Uffizzi
|
||||
needs:
|
||||
- cache-compose-file
|
||||
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2.6.1
|
||||
with:
|
||||
# If this workflow was triggered by a PR close event, cache-key will be an empty string
|
||||
# and this reusable workflow will delete the preview deployment.
|
||||
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
|
||||
compose-file-cache-path: docker-compose.rendered.yml
|
||||
server: https://app.uffizzi.com/
|
||||
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ Homestead.yaml
|
||||
.gitkeep
|
||||
/public/docs
|
||||
/.scribe
|
||||
!storage/fonts/.gitkeep
|
||||
.DS_Store
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM php:7.4-fpm
|
||||
FROM php:8.1-fpm
|
||||
|
||||
# Arguments defined in docker-compose.yml
|
||||
ARG user
|
||||
|
||||
@ -55,7 +55,7 @@ class CreateTemplateCommand extends Command
|
||||
copy(public_path("/build/img/PDF/{$type}1.png"), public_path("/build/img/PDF/{$templateName}.png"));
|
||||
copy(resource_path("/static/img/PDF/{$type}1.png"), resource_path("/static/img/PDF/{$templateName}.png"));
|
||||
|
||||
$path = resource_path("app/pdf/{$type}/{$templateName}.blade.php");
|
||||
$path = resource_path("views/app/pdf/{$type}/{$templateName}.blade.php");
|
||||
$type = ucfirst($type);
|
||||
$this->info("{$type} Template created successfully at ".$path);
|
||||
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\MailSender;
|
||||
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Http\Resources\MailSenderResource;
|
||||
use Crater\Models\MailSender;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GetAllMailSendersController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$mailSenders = MailSender::whereCompany()->get();
|
||||
|
||||
return MailSenderResource::collection($mailSenders);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\MailSender;
|
||||
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Http\Requests\MailSenderRequest;
|
||||
use Crater\Http\Resources\MailSenderResource;
|
||||
use Crater\Models\MailSender;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MailSenderController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('viewAny', MailSender::class);
|
||||
|
||||
$limit = $request->has('limit') ? $request->limit : 10;
|
||||
|
||||
$mailSenders = MailSender::whereCompany()
|
||||
->applyFilters($request->all())
|
||||
->paginateData($limit);
|
||||
|
||||
return (MailSenderResource::collection($mailSenders))
|
||||
->additional(['meta' => [
|
||||
'mail_sender_total_count' => MailSender::whereCompany()->count(),
|
||||
]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(MailSenderRequest $request)
|
||||
{
|
||||
$this->authorize('create', MailSender::class);
|
||||
|
||||
$mailSender = MailSender::createFromRequest($request);
|
||||
|
||||
return new MailSenderResource($mailSender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Crater\Models\SenderMail $senderMail
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(MailSender $mailSender)
|
||||
{
|
||||
$this->authorize('view', $mailSender);
|
||||
|
||||
return new MailSenderResource($mailSender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Crater\Models\SenderMail $senderMail
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(MailSenderRequest $request, MailSender $mailSender)
|
||||
{
|
||||
$this->authorize('update', $mailSender);
|
||||
|
||||
$mailSender->updateFromRequest($request);
|
||||
|
||||
return new MailSenderResource($mailSender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \Crater\Models\SenderMail $senderMail
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(MailSender $mailSender)
|
||||
{
|
||||
$this->authorize('delete', $mailSender);
|
||||
|
||||
if ($mailSender->is_default) {
|
||||
return respondJson('You can\'t remove default mail sender.', 'You can\'t remove default mail sender.');
|
||||
}
|
||||
|
||||
$mailSender->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -2,24 +2,25 @@
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\Report;
|
||||
|
||||
use PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Currency;
|
||||
use Crater\Models\Customer;
|
||||
use Illuminate\Http\Request;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PDF;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
|
||||
class CustomerSalesReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request, $hash)
|
||||
{
|
||||
$company = Company::where('unique_hash', $hash)->first();
|
||||
@ -56,6 +57,7 @@ class CustomerSalesReportController extends Controller
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
|
||||
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
|
||||
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
|
||||
|
||||
$colors = [
|
||||
'primary_text_color',
|
||||
@ -80,6 +82,7 @@ class CustomerSalesReportController extends Controller
|
||||
'company' => $company,
|
||||
'from_date' => $from_date,
|
||||
'to_date' => $to_date,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
|
||||
$pdf = PDF::loadView('app.pdf.reports.sales-customers');
|
||||
|
||||
@ -2,24 +2,25 @@
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Expense;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\Expense;
|
||||
use Crater\Models\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
|
||||
class ExpensesReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request, $hash)
|
||||
{
|
||||
$company = Company::where('unique_hash', $hash)->first();
|
||||
@ -43,6 +44,7 @@ class ExpensesReportController extends Controller
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
|
||||
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
|
||||
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
|
||||
|
||||
$colors = [
|
||||
'primary_text_color',
|
||||
@ -66,6 +68,7 @@ class ExpensesReportController extends Controller
|
||||
'company' => $company,
|
||||
'from_date' => $from_date,
|
||||
'to_date' => $to_date,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
$pdf = PDF::loadView('app.pdf.reports.expenses');
|
||||
|
||||
|
||||
@ -2,24 +2,25 @@
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Crater\Models\InvoiceItem;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Crater\Models\InvoiceItem;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
|
||||
class ItemSalesReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request, $hash)
|
||||
{
|
||||
$company = Company::where('unique_hash', $hash)->first();
|
||||
@ -43,6 +44,7 @@ class ItemSalesReportController extends Controller
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
|
||||
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
|
||||
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
|
||||
|
||||
$colors = [
|
||||
'primary_text_color',
|
||||
@ -66,6 +68,7 @@ class ItemSalesReportController extends Controller
|
||||
'company' => $company,
|
||||
'from_date' => $from_date,
|
||||
'to_date' => $to_date,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
$pdf = PDF::loadView('app.pdf.reports.sales-items');
|
||||
|
||||
|
||||
@ -2,25 +2,26 @@
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\Report;
|
||||
|
||||
use PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Expense;
|
||||
use Crater\Models\Payment;
|
||||
use Crater\Models\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PDF;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
|
||||
class ProfitLossReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request, $hash)
|
||||
{
|
||||
$company = Company::where('unique_hash', $hash)->first();
|
||||
@ -49,6 +50,8 @@ class ProfitLossReportController extends Controller
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
|
||||
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
|
||||
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
|
||||
|
||||
|
||||
$colors = [
|
||||
'primary_text_color',
|
||||
@ -74,6 +77,7 @@ class ProfitLossReportController extends Controller
|
||||
'company' => $company,
|
||||
'from_date' => $from_date,
|
||||
'to_date' => $to_date,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
$pdf = PDF::loadView('app.pdf.reports.profit-loss');
|
||||
|
||||
|
||||
@ -2,24 +2,25 @@
|
||||
|
||||
namespace Crater\Http\Controllers\V1\Admin\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Tax;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Models\Tax;
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Crater\Models\CompanySetting;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Crater\Http\Controllers\Controller;
|
||||
|
||||
class TaxSummaryReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $hash
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request, $hash)
|
||||
{
|
||||
$company = Company::where('unique_hash', $hash)->first();
|
||||
@ -44,6 +45,8 @@ class TaxSummaryReportController extends Controller
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
|
||||
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
|
||||
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
|
||||
|
||||
|
||||
$colors = [
|
||||
'primary_text_color',
|
||||
@ -68,6 +71,7 @@ class TaxSummaryReportController extends Controller
|
||||
'company' => $company,
|
||||
'from_date' => $from_date,
|
||||
'to_date' => $to_date,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
|
||||
$pdf = PDF::loadView('app.pdf.reports.tax-summary');
|
||||
|
||||
@ -3,80 +3,29 @@
|
||||
namespace Crater\Http\Controllers\V1\Admin\Settings;
|
||||
|
||||
use Crater\Http\Controllers\Controller;
|
||||
use Crater\Http\Requests\MailEnvironmentRequest;
|
||||
use Crater\Http\Requests\TestMailDriverRequest;
|
||||
use Crater\Mail\TestMail;
|
||||
use Crater\Models\Setting;
|
||||
use Crater\Space\EnvironmentManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Crater\Models\MailSender;
|
||||
use Illuminate\Http\Request;
|
||||
use Mail;
|
||||
|
||||
class MailConfigurationController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var EnvironmentManager
|
||||
*/
|
||||
protected $environmentManager;
|
||||
|
||||
/**
|
||||
* @param EnvironmentManager $environmentManager
|
||||
*/
|
||||
public function __construct(EnvironmentManager $environmentManager)
|
||||
{
|
||||
$this->environmentManager = $environmentManager;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param MailEnvironmentRequest $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function saveMailEnvironment(MailEnvironmentRequest $request)
|
||||
public function TestMailDriver(TestMailDriverRequest $request)
|
||||
{
|
||||
$this->authorize('manage email config');
|
||||
|
||||
$setting = Setting::getSetting('profile_complete');
|
||||
$results = $this->environmentManager->saveMailVariables($request);
|
||||
MailSender::setMailConfiguration($request->mail_sender_id);
|
||||
|
||||
if ($setting !== 'COMPLETED') {
|
||||
Setting::setSetting('profile_complete', 4);
|
||||
}
|
||||
Mail::to($request->to)->send(new TestMail($request->subject, $request->message));
|
||||
|
||||
return response()->json($results);
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMailEnvironment()
|
||||
public function getMailDrivers(Request $request)
|
||||
{
|
||||
$this->authorize('manage email config');
|
||||
|
||||
$MailData = [
|
||||
'mail_driver' => config('mail.driver'),
|
||||
'mail_host' => config('mail.host'),
|
||||
'mail_port' => config('mail.port'),
|
||||
'mail_username' => config('mail.username'),
|
||||
'mail_password' => config('mail.password'),
|
||||
'mail_encryption' => config('mail.encryption'),
|
||||
'from_name' => config('mail.from.name'),
|
||||
'from_mail' => config('mail.from.address'),
|
||||
'mail_mailgun_endpoint' => config('services.mailgun.endpoint'),
|
||||
'mail_mailgun_domain' => config('services.mailgun.domain'),
|
||||
'mail_mailgun_secret' => config('services.mailgun.secret'),
|
||||
'mail_ses_key' => config('services.ses.key'),
|
||||
'mail_ses_secret' => config('services.ses.secret'),
|
||||
];
|
||||
|
||||
|
||||
return response()->json($MailData);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getMailDrivers()
|
||||
{
|
||||
$this->authorize('manage email config');
|
||||
|
||||
$drivers = [
|
||||
'smtp',
|
||||
'mail',
|
||||
@ -87,21 +36,4 @@ class MailConfigurationController extends Controller
|
||||
|
||||
return response()->json($drivers);
|
||||
}
|
||||
|
||||
public function testEmailConfig(Request $request)
|
||||
{
|
||||
$this->authorize('manage email config');
|
||||
|
||||
$this->validate($request, [
|
||||
'to' => 'required|email',
|
||||
'subject' => 'required',
|
||||
'message' => 'required',
|
||||
]);
|
||||
|
||||
Mail::to($request->to)->send(new TestMail($request->subject, $request->message));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Customer;
|
||||
use Crater\Models\EmailLog;
|
||||
use Crater\Models\Estimate;
|
||||
use Crater\Models\MailSender;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EstimatePdfController extends Controller
|
||||
@ -27,14 +28,16 @@ class EstimatePdfController extends Controller
|
||||
);
|
||||
|
||||
if ($notifyEstimateViewed == 'YES') {
|
||||
$data['estimate'] = Estimate::findOrFail($estimate->id)->toArray();
|
||||
$data['user'] = Customer::find($estimate->customer_id)->toArray();
|
||||
$notificationEmail = CompanySetting::getSetting(
|
||||
'notification_email',
|
||||
$estimate->company_id
|
||||
);
|
||||
$notificationEmail = CompanySetting::getSetting('notification_email', $estimate->company_id);
|
||||
$mailSender = MailSender::where('company_id', $estimate->company_id)->where('is_default', true)->first();
|
||||
MailSender::setMailConfiguration($mailSender->id);
|
||||
|
||||
\Mail::to($notificationEmail)->send(new EstimateViewedMail($data));
|
||||
$data['from_address'] = $mailSender->from_address;
|
||||
$data['from_name'] = $mailSender->from_name;
|
||||
$data['user'] = Customer::find($estimate->customer_id)->toArray();
|
||||
$data['estimate'] = Estimate::findOrFail($estimate->id)->toArray();
|
||||
|
||||
send_mail(new EstimateViewedMail($data), $mailSender, $notificationEmail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ use Crater\Models\CompanySetting;
|
||||
use Crater\Models\Customer;
|
||||
use Crater\Models\EmailLog;
|
||||
use Crater\Models\Invoice;
|
||||
use Crater\Models\MailSender;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InvoicePdfController extends Controller
|
||||
@ -28,14 +29,16 @@ class InvoicePdfController extends Controller
|
||||
);
|
||||
|
||||
if ($notifyInvoiceViewed == 'YES') {
|
||||
$notificationEmail = CompanySetting::getSetting('notification_email', $invoice->company_id);
|
||||
$mailSender = MailSender::where('company_id', $invoice->company_id)->where('is_default', true)->first();
|
||||
MailSender::setMailConfiguration($mailSender->id);
|
||||
|
||||
$data['from_address'] = $mailSender->from_address;
|
||||
$data['from_name'] = $mailSender->from_name;
|
||||
$data['invoice'] = Invoice::findOrFail($invoice->id)->toArray();
|
||||
$data['user'] = Customer::find($invoice->customer_id)->toArray();
|
||||
$notificationEmail = CompanySetting::getSetting(
|
||||
'notification_email',
|
||||
$invoice->company_id
|
||||
);
|
||||
|
||||
\Mail::to($notificationEmail)->send(new InvoiceViewedMail($data));
|
||||
send_mail(new InvoiceViewedMail($data), $mailSender, $notificationEmail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ namespace Crater\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Crater\Models\FileDisk;
|
||||
use Crater\Models\MailSender;
|
||||
|
||||
class ConfigMiddleware
|
||||
{
|
||||
@ -28,6 +29,12 @@ class ConfigMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
$default_mail_sender = MailSender::where('company_id', $request->header('company'))->where('is_default', true)->first();
|
||||
|
||||
if ($default_mail_sender) {
|
||||
$default_mail_sender->setMailConfiguration($default_mail_sender->id);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
85
app/Http/Requests/MailSenderRequest.php
Normal file
85
app/Http/Requests/MailSenderRequest.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MailSenderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
Rule::unique('mail_senders')
|
||||
->where('company_id', $this->header('company'))
|
||||
],
|
||||
'driver' => [
|
||||
'required',
|
||||
],
|
||||
'is_default' => [
|
||||
'nullable'
|
||||
],
|
||||
'bcc' => [
|
||||
'nullable'
|
||||
],
|
||||
'cc' => [
|
||||
'nullable'
|
||||
],
|
||||
'from_address' => [
|
||||
'nullable'
|
||||
],
|
||||
'from_name' => [
|
||||
'nullable'
|
||||
],
|
||||
'settings' => [
|
||||
'nullable'
|
||||
],
|
||||
'settings.*' => [
|
||||
'nullable'
|
||||
]
|
||||
];
|
||||
|
||||
if ($this->isMethod('PUT')) {
|
||||
$rules['name'] = [
|
||||
'nullable',
|
||||
Rule::unique('mail_senders')
|
||||
->ignore($this->route('mail_sender')->id)
|
||||
->where('company_id', $this->header('company'))
|
||||
];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getMailSenderPayload()
|
||||
{
|
||||
$data = $this->validated();
|
||||
|
||||
if ($data['settings'] && $data['settings']['encryption'] == 'none') {
|
||||
$data['settings']['encryption'] = '';
|
||||
}
|
||||
|
||||
return collect($data)
|
||||
->merge([
|
||||
'company_id' => $this->header('company'),
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ class SendEstimatesRequest extends FormRequest
|
||||
'body' => [
|
||||
'required',
|
||||
],
|
||||
'from' => [
|
||||
'mail_sender_id' => [
|
||||
'required',
|
||||
],
|
||||
'to' => [
|
||||
|
||||
@ -30,7 +30,7 @@ class SendInvoiceRequest extends FormRequest
|
||||
'subject' => [
|
||||
'required',
|
||||
],
|
||||
'from' => [
|
||||
'mail_sender_id' => [
|
||||
'required',
|
||||
],
|
||||
'to' => [
|
||||
|
||||
@ -30,7 +30,7 @@ class SendPaymentRequest extends FormRequest
|
||||
'body' => [
|
||||
'required',
|
||||
],
|
||||
'from' => [
|
||||
'mail_sender_id' => [
|
||||
'required',
|
||||
],
|
||||
'to' => [
|
||||
|
||||
39
app/Http/Requests/TestMailDriverRequest.php
Normal file
39
app/Http/Requests/TestMailDriverRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TestMailDriverRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'to' => [
|
||||
'required',
|
||||
'email'
|
||||
],
|
||||
'subject' => [
|
||||
'required'
|
||||
],
|
||||
'message' => [
|
||||
'required'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Resources/MailSenderResource.php
Normal file
30
app/Http/Resources/MailSenderResource.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class MailSenderResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'driver' => $this->driver,
|
||||
'is_default' => $this->is_default,
|
||||
'bcc' => $this->bcc,
|
||||
'cc' => $this->cc,
|
||||
'from_address' => $this->from_address,
|
||||
'from_name' => $this->from_name,
|
||||
'company_id' => $this->company_id,
|
||||
'settings' => $this->settings
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ class EstimateViewedMail extends Mailable
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
return $this->from($this->data['from_address'], $this->data['from_name'])
|
||||
->markdown('emails.viewed.estimate', ['data', $this->data]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ class InvoiceViewedMail extends Mailable
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
return $this->from($this->data['from_address'], $this->data['from_name'])
|
||||
->markdown('emails.viewed.invoice', ['data', $this->data]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ class SendEstimateMail extends Mailable
|
||||
public function build()
|
||||
{
|
||||
$log = EmailLog::create([
|
||||
'from' => $this->data['from'],
|
||||
'from' => $this->data['from_address'],
|
||||
'to' => $this->data['to'],
|
||||
'subject' => $this->data['subject'],
|
||||
'body' => $this->data['body'],
|
||||
@ -47,9 +47,10 @@ class SendEstimateMail extends Mailable
|
||||
|
||||
$this->data['url'] = route('estimate', ['email_log' => $log->token]);
|
||||
|
||||
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
|
||||
->subject($this->data['subject'])
|
||||
->markdown('emails.send.estimate', ['data', $this->data]);
|
||||
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
|
||||
->subject($this->data['subject'])
|
||||
->markdown("emails.send.estimate", ['data', $this->data]);
|
||||
|
||||
|
||||
if ($this->data['attach']['data']) {
|
||||
$mailContent->attachData(
|
||||
|
||||
@ -34,7 +34,7 @@ class SendInvoiceMail extends Mailable
|
||||
public function build()
|
||||
{
|
||||
$log = EmailLog::create([
|
||||
'from' => $this->data['from'],
|
||||
'from' => $this->data['from_address'],
|
||||
'to' => $this->data['to'],
|
||||
'subject' => $this->data['subject'],
|
||||
'body' => $this->data['body'],
|
||||
@ -47,9 +47,9 @@ class SendInvoiceMail extends Mailable
|
||||
|
||||
$this->data['url'] = route('invoice', ['email_log' => $log->token]);
|
||||
|
||||
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
|
||||
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
|
||||
->subject($this->data['subject'])
|
||||
->markdown('emails.send.invoice', ['data', $this->data]);
|
||||
->markdown("emails.send.invoice", ['data', $this->data]);
|
||||
|
||||
if ($this->data['attach']['data']) {
|
||||
$mailContent->attachData(
|
||||
|
||||
@ -34,7 +34,7 @@ class SendPaymentMail extends Mailable
|
||||
public function build()
|
||||
{
|
||||
$log = EmailLog::create([
|
||||
'from' => $this->data['from'],
|
||||
'from' => $this->data['from_address'],
|
||||
'to' => $this->data['to'],
|
||||
'subject' => $this->data['subject'],
|
||||
'body' => $this->data['body'],
|
||||
@ -47,9 +47,9 @@ class SendPaymentMail extends Mailable
|
||||
|
||||
$this->data['url'] = route('payment', ['email_log' => $log->token]);
|
||||
|
||||
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
|
||||
->subject($this->data['subject'])
|
||||
->markdown('emails.send.payment', ['data', $this->data]);
|
||||
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
|
||||
->subject($this->data['subject'])
|
||||
->markdown("emails.send.payment", ['data', $this->data]);
|
||||
|
||||
if ($this->data['attach']['data']) {
|
||||
$mailContent->attachData(
|
||||
|
||||
@ -5,10 +5,10 @@ namespace Crater\Models;
|
||||
use App;
|
||||
use Barryvdh\DomPDF\Facade as PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Mail\SendEstimateMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Crater\Traits\MailTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -20,6 +20,7 @@ use Vinkla\Hashids\Facades\Hashids;
|
||||
class Estimate extends Model implements HasMedia
|
||||
{
|
||||
use HasFactory;
|
||||
use MailTrait;
|
||||
use InteractsWithMedia;
|
||||
use GeneratesPdfTrait;
|
||||
use HasCustomFieldsTrait;
|
||||
@ -363,7 +364,7 @@ class Estimate extends Model implements HasMedia
|
||||
$this->save();
|
||||
}
|
||||
|
||||
\Mail::to($data['to'])->send(new SendEstimateMail($data));
|
||||
$this->setMail('estimate', $data);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
|
||||
@ -9,6 +9,7 @@ use Crater\Mail\SendInvoiceMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Crater\Traits\MailTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -21,6 +22,7 @@ use Vinkla\Hashids\Facades\Hashids;
|
||||
class Invoice extends Model implements HasMedia
|
||||
{
|
||||
use HasFactory;
|
||||
use MailTrait;
|
||||
use InteractsWithMedia;
|
||||
use GeneratesPdfTrait;
|
||||
use HasCustomFieldsTrait;
|
||||
@ -464,7 +466,7 @@ class Invoice extends Model implements HasMedia
|
||||
{
|
||||
$data = $this->sendInvoiceData($data);
|
||||
|
||||
\Mail::to($data['to'])->send(new SendInvoiceMail($data));
|
||||
$this->setMail('invoice', $data);
|
||||
|
||||
if ($this->status == Invoice::STATUS_DRAFT) {
|
||||
$this->status = Invoice::STATUS_SENT;
|
||||
|
||||
111
app/Models/MailSender.php
Normal file
111
app/Models/MailSender.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Crater\Http\Requests\MailSenderRequest;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Config;
|
||||
|
||||
class MailSender extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'settings' => 'array',
|
||||
'is_default' => 'boolean'
|
||||
];
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function scopeWhereOrder($query, $orderByField, $orderBy)
|
||||
{
|
||||
$query->orderBy($orderByField, $orderBy);
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('mail_senders.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public static function createFromRequest(MailSenderRequest $request)
|
||||
{
|
||||
$senderMail = self::create($request->getMailSenderPayload());
|
||||
|
||||
if ($request->is_default) {
|
||||
$senderMail->removeOtherDefaultMailSenders($request);
|
||||
}
|
||||
|
||||
return $senderMail;
|
||||
}
|
||||
|
||||
public function updateFromRequest(MailSenderRequest $request)
|
||||
{
|
||||
$data = $request->getMailSenderPayload();
|
||||
|
||||
$this->update($data);
|
||||
|
||||
if ($request->is_default) {
|
||||
$this->removeOtherDefaultMailSenders($request);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function setMailConfiguration($id, $check = null)
|
||||
{
|
||||
$mailSender = MailSender::find($id);
|
||||
|
||||
$settings = $mailSender->settings;
|
||||
$settings['driver'] = $mailSender->driver;
|
||||
$settings['from'] = [
|
||||
'address' => $mailSender->from_address,
|
||||
'name' => $mailSender->from_name
|
||||
];
|
||||
$settings['sendmail'] = config('mail.sendmail');
|
||||
$settings['markdown'] = config('mail.markdown');
|
||||
$settings['log_channel'] = config('mail.log_channel');
|
||||
|
||||
Config::set('mail', $settings);
|
||||
|
||||
if ($check) {
|
||||
return $mailSender;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeOtherDefaultMailSenders($request) {
|
||||
MailSender::where('company_id', $request->header('company'))
|
||||
->where('is_default', true)
|
||||
->where('id', '<>', $this->id)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,10 @@ namespace Crater\Models;
|
||||
use Barryvdh\DomPDF\Facade as PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Jobs\GeneratePaymentPdfJob;
|
||||
use Crater\Mail\SendPaymentMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Crater\Traits\MailTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
@ -18,6 +18,7 @@ use Vinkla\Hashids\Facades\Hashids;
|
||||
class Payment extends Model implements HasMedia
|
||||
{
|
||||
use HasFactory;
|
||||
use MailTrait;
|
||||
use InteractsWithMedia;
|
||||
use GeneratesPdfTrait;
|
||||
use HasCustomFieldsTrait;
|
||||
@ -135,7 +136,7 @@ class Payment extends Model implements HasMedia
|
||||
{
|
||||
$data = $this->sendPaymentData($data);
|
||||
|
||||
\Mail::to($data['to'])->send(new SendPaymentMail($data));
|
||||
$this->setMail('payment', $data);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
|
||||
123
app/Policies/MailSenderPolicy.php
Normal file
123
app/Policies/MailSenderPolicy.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Policies;
|
||||
|
||||
use Crater\Models\MailSender;
|
||||
use Crater\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
|
||||
class MailSenderPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function viewAny(User $user)
|
||||
{
|
||||
if (BouncerFacade::can('view-mail-sender', MailSender::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @param \Crater\Models\MailSender $mailSender
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function view(User $user, MailSender $mailSender)
|
||||
{
|
||||
if (BouncerFacade::can('view-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
if (BouncerFacade::can('create-mail-sender', MailSender::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @param \Crater\Models\MailSender $mailSender
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function update(User $user, MailSender $mailSender)
|
||||
{
|
||||
if (BouncerFacade::can('edit-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @param \Crater\Models\MailSender $mailSender
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function delete(User $user, MailSender $mailSender)
|
||||
{
|
||||
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @param \Crater\Models\MailSender $mailSender
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function restore(User $user, MailSender $mailSender)
|
||||
{
|
||||
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \Crater\Models\User $user
|
||||
* @param \Crater\Models\MailSender $mailSender
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function forceDelete(User $user, MailSender $mailSender)
|
||||
{
|
||||
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
\Crater\Models\CustomField::class => \Crater\Policies\CustomFieldPolicy::class,
|
||||
\Crater\Models\User::class => \Crater\Policies\UserPolicy::class,
|
||||
\Crater\Models\Item::class => \Crater\Policies\ItemPolicy::class,
|
||||
\Crater\Models\MailSender::class => \Crater\Policies\MailSenderPolicy::class,
|
||||
\Silber\Bouncer\Database\Role::class => \Crater\Policies\RolePolicy::class,
|
||||
\Crater\Models\Unit::class => \Crater\Policies\UnitPolicy::class,
|
||||
\Crater\Models\RecurringInvoice::class => \Crater\Policies\RecurringInvoicePolicy::class,
|
||||
|
||||
@ -223,204 +223,6 @@ class EnvironmentManager
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the mail content to the .env file.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function saveMailVariables(MailEnvironmentRequest $request)
|
||||
{
|
||||
$mailData = $this->getMailData($request);
|
||||
|
||||
try {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$mailData['old_mail_data'],
|
||||
$mailData['new_mail_data'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
if ($mailData['extra_old_mail_data']) {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$mailData['extra_old_mail_data'],
|
||||
$mailData['extra_mail_data'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
} else {
|
||||
file_put_contents(
|
||||
$this->envPath,
|
||||
"\n".$mailData['extra_mail_data'],
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'mail_variables_save_error',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => 'mail_variables_save_successfully',
|
||||
];
|
||||
}
|
||||
|
||||
private function getMailData($request)
|
||||
{
|
||||
$mailFromCredential = "";
|
||||
$extraMailData = "";
|
||||
$extraOldMailData = "";
|
||||
$oldMailData = "";
|
||||
$newMailData = "";
|
||||
|
||||
if (env('MAIL_FROM_ADDRESS') !== null && env('MAIL_FROM_NAME') !== null) {
|
||||
$mailFromCredential =
|
||||
'MAIL_FROM_ADDRESS='.config('mail.from.address')."\n".
|
||||
'MAIL_FROM_NAME="'.config('mail.from.name')."\"\n\n";
|
||||
}
|
||||
|
||||
switch ($request->mail_driver) {
|
||||
case 'smtp':
|
||||
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.$request->mail_username."\n".
|
||||
'MAIL_PASSWORD='.$request->mail_password."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'mailgun':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
$extraMailData =
|
||||
'MAILGUN_DOMAIN='.$request->mail_mailgun_domain."\n".
|
||||
'MAILGUN_SECRET='.$request->mail_mailgun_secret."\n".
|
||||
'MAILGUN_ENDPOINT='.$request->mail_mailgun_endpoint."\n";
|
||||
|
||||
if (env('MAILGUN_DOMAIN') !== null && env('MAILGUN_SECRET') !== null && env('MAILGUN_ENDPOINT') !== null) {
|
||||
$extraOldMailData =
|
||||
'MAILGUN_DOMAIN='.config('services.mailgun.domain')."\n".
|
||||
'MAILGUN_SECRET='.config('services.mailgun.secret')."\n".
|
||||
'MAILGUN_ENDPOINT='.config('services.mailgun.endpoint')."\n";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'ses':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
$extraMailData =
|
||||
'SES_KEY='.$request->mail_ses_key."\n".
|
||||
'SES_SECRET='.$request->mail_ses_secret."\n";
|
||||
|
||||
if (env('SES_KEY') !== null && env('SES_SECRET') !== null) {
|
||||
$extraOldMailData =
|
||||
'SES_KEY='.config('services.ses.key')."\n".
|
||||
'SES_SECRET='.config('services.ses.secret')."\n";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'mail':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'sendmail':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'old_mail_data' => $oldMailData,
|
||||
'new_mail_data' => $newMailData,
|
||||
'extra_mail_data' => $extraMailData,
|
||||
'extra_old_mail_data' => $extraOldMailData,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the disk content to the .env file.
|
||||
*
|
||||
|
||||
@ -5,6 +5,7 @@ use Crater\Models\Currency;
|
||||
use Crater\Models\CustomField;
|
||||
use Crater\Models\Setting;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
/**
|
||||
* Get company setting
|
||||
@ -70,6 +71,42 @@ function set_active($path, $active = 'active')
|
||||
return call_user_func_array('Request::is', (array)$path) ? $active : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Mail
|
||||
*
|
||||
* @param Mailable $mailable
|
||||
* @param object $mailSender
|
||||
* @return string $to
|
||||
*/
|
||||
function send_mail(Mailable $mailable, object $mailSender = null, string $to)
|
||||
{
|
||||
if ($mailSender->bcc && $mailSender->cc) {
|
||||
\Mail::to($to)
|
||||
->bcc(explode(',', $mailSender->bcc))
|
||||
->cc(explode(',', $mailSender->cc))
|
||||
->send($mailable);
|
||||
}
|
||||
|
||||
if ($mailSender->bcc && $mailSender->cc == null) {
|
||||
\Mail::to($to)
|
||||
->bcc(explode(',', $mailSender->bcc))
|
||||
->send($mailable);
|
||||
}
|
||||
|
||||
if ($mailSender->bcc == null && $mailSender->cc) {
|
||||
\Mail::to($to)
|
||||
->cc(explode(',', $mailSender->cc))
|
||||
->send($mailable);
|
||||
}
|
||||
|
||||
if ($mailSender->bcc == null && $mailSender->cc == null) {
|
||||
\Mail::to($to)
|
||||
->send($mailable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @return mixed
|
||||
|
||||
40
app/Traits/MailTrait.php
Normal file
40
app/Traits/MailTrait.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Traits;
|
||||
|
||||
use Crater\Mail\EstimateViewedMail;
|
||||
use Crater\Mail\InvoiceViewedMail;
|
||||
use Crater\Mail\SendEstimateMail;
|
||||
use Crater\Mail\SendInvoiceMail;
|
||||
use Crater\Mail\SendPaymentMail;
|
||||
use Crater\Models\MailSender;
|
||||
|
||||
trait MailTrait
|
||||
{
|
||||
public function setMail($model, $data)
|
||||
{
|
||||
$mailSender = MailSender::setMailConfiguration($data['mail_sender_id'], true);
|
||||
|
||||
$data['from_address'] = $mailSender->from_address;
|
||||
$data['from_name'] = $mailSender->from_name;
|
||||
|
||||
switch ($model) {
|
||||
case 'invoice':
|
||||
send_mail(new SendInvoiceMail($data), $mailSender, $data['to']);
|
||||
|
||||
break;
|
||||
|
||||
case 'estimate':
|
||||
send_mail(new SendEstimateMail($data), $mailSender, $data['to']);
|
||||
|
||||
break;
|
||||
|
||||
case 'payment':
|
||||
send_mail(new SendPaymentMail($data), $mailSender, $data['to']);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -38,15 +38,15 @@
|
||||
"barryvdh/laravel-ide-helper": "^2.6",
|
||||
"beyondcode/laravel-dump-server": "^1.0",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"fzaninotto/faker": "^1.9.1",
|
||||
"friendsofphp/php-cs-fixer": "^3.8",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"pestphp/pest": "^1.0",
|
||||
"pestphp/pest-plugin-faker": "^1.0",
|
||||
"pestphp/pest-plugin-laravel": "^1.0",
|
||||
"pestphp/pest-plugin-parallel": "^0.2.1",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -81,11 +81,14 @@
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2347
composer.lock
generated
2347
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ use Crater\Models\ExchangeRateProvider;
|
||||
use Crater\Models\Expense;
|
||||
use Crater\Models\Invoice;
|
||||
use Crater\Models\Item;
|
||||
use Crater\Models\MailSender;
|
||||
use Crater\Models\Note;
|
||||
use Crater\Models\Payment;
|
||||
use Crater\Models\RecurringInvoice;
|
||||
@ -397,6 +398,41 @@ return [
|
||||
]
|
||||
],
|
||||
|
||||
// Mail Sender
|
||||
[
|
||||
"name" => "view mail sender",
|
||||
"ability" => "view-mail-sender",
|
||||
"model" => MailSender::class,
|
||||
'owner_only' => false,
|
||||
],
|
||||
[
|
||||
"name" => "create mail sender",
|
||||
"ability" => "create-mail-sender",
|
||||
"model" => MailSender::class,
|
||||
'owner_only' => false,
|
||||
"depends_on" => [
|
||||
'view-mail-sender',
|
||||
]
|
||||
],
|
||||
[
|
||||
"name" => "edit mail sender",
|
||||
"ability" => "edit-mail-sender",
|
||||
"model" => MailSender::class,
|
||||
'owner_only' => false,
|
||||
"depends_on" => [
|
||||
'view-mail-sender',
|
||||
]
|
||||
],
|
||||
[
|
||||
"name" => "delete mail sender",
|
||||
"ability" => "delete-mail-sender",
|
||||
"model" => MailSender::class,
|
||||
'owner_only' => false,
|
||||
"depends_on" => [
|
||||
'view-mail-sender',
|
||||
]
|
||||
],
|
||||
|
||||
// Settings
|
||||
[
|
||||
"name" => "view company dashboard",
|
||||
|
||||
@ -7,6 +7,7 @@ use Crater\Models\ExchangeRateProvider;
|
||||
use Crater\Models\Expense;
|
||||
use Crater\Models\Invoice;
|
||||
use Crater\Models\Item;
|
||||
use Crater\Models\MailSender;
|
||||
use Crater\Models\Note;
|
||||
use Crater\Models\Payment;
|
||||
use Crater\Models\RecurringInvoice;
|
||||
@ -71,6 +72,7 @@ return [
|
||||
["code" => "cs", "name" => "Czech"],
|
||||
["code" => "el", "name" => "Greek"],
|
||||
["code" => "hr", "name" => "Crotian"],
|
||||
["code" => "th", "name" => "ไทย"],
|
||||
],
|
||||
|
||||
/*
|
||||
@ -224,6 +226,17 @@ return [
|
||||
'ability' => 'view-all-notes',
|
||||
'model' => Note::class
|
||||
],
|
||||
[
|
||||
'title' => 'settings.menu_title.mail_sender',
|
||||
'group' => '',
|
||||
'name' => 'Mail Sender',
|
||||
'link' => '/admin/settings/mail-sender',
|
||||
'icon' => 'MailIcon',
|
||||
'owner_only' => false,
|
||||
'ability' => 'view-mail-sender',
|
||||
'model' => MailSender::class
|
||||
],
|
||||
|
||||
[
|
||||
'title' => 'settings.menu_title.expense_category',
|
||||
'group' => '',
|
||||
@ -234,16 +247,6 @@ return [
|
||||
'ability' => 'view-expense',
|
||||
'model' => Expense::class
|
||||
],
|
||||
[
|
||||
'title' => 'settings.mail.mail_config',
|
||||
'group' => '',
|
||||
'name' => 'Mail Configuration',
|
||||
'link' => '/admin/settings/mail-configuration',
|
||||
'icon' => 'MailIcon',
|
||||
'owner_only' => true,
|
||||
'ability' => '',
|
||||
'model' => ''
|
||||
],
|
||||
[
|
||||
'title' => 'settings.menu_title.file_disk',
|
||||
'group' => '',
|
||||
@ -274,6 +277,7 @@ return [
|
||||
'ability' => '',
|
||||
'model' => ''
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@ -27,6 +27,7 @@ return [
|
||||
'tokenizer',
|
||||
'JSON',
|
||||
'cURL',
|
||||
'zip',
|
||||
],
|
||||
'apache' => [
|
||||
'mod_rewrite',
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
use Crater\Models\Company;
|
||||
use Crater\Models\MailSender;
|
||||
use Crater\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
|
||||
class CreateMailSendersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('mail_senders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('driver');
|
||||
$table->boolean('is_default')->default(false);
|
||||
$table->string('bcc')->nullable();
|
||||
$table->string('cc')->nullable();
|
||||
$table->string('from_address')->nullable();
|
||||
$table->string('from_name')->nullable();
|
||||
$table->json('settings')->nullable();
|
||||
$table->integer('company_id')->unsigned()->nullable();
|
||||
$table->foreign('company_id')->references('id')->on('companies');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$users = User::where('role', 'super admin')->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
BouncerFacade::allow($user)->toManage(MailSender::class);
|
||||
}
|
||||
|
||||
$companies = Company::all();
|
||||
|
||||
$companies->map(function ($company) {
|
||||
if (env('MAIL_DRIVER') == 'smtp') {
|
||||
$settings = [
|
||||
'MAIL_HOST' => env('MAIL_HOST'),
|
||||
'MAIL_PORT' => env('MAIL_PORT'),
|
||||
'MAIL_USERNAME' => env('MAIL_USERNAME'),
|
||||
'MAIL_PASSWORD' => env('MAIL_PASSWORD'),
|
||||
'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION')
|
||||
];
|
||||
$this->createSender($settings, $company->id);
|
||||
}
|
||||
|
||||
if (env('MAIL_DRIVER') == 'mail' || env('MAIL_DRIVER') == 'sendmail') {
|
||||
$this->createSender(null, $company->id);
|
||||
}
|
||||
|
||||
if (env('MAIL_DRIVER') == 'mailgun') {
|
||||
$settings = [
|
||||
'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'),
|
||||
'MAILGUN_SECRET' => env('MAILGUN_SECRET'),
|
||||
'MAILGUN_ENDPOINT' => env('MAILGUN_ENDPOINT'),
|
||||
];
|
||||
$this->createSender($settings, $company->id);
|
||||
}
|
||||
|
||||
if (env('MAIL_DRIVER') == 'ses') {
|
||||
$settings = [
|
||||
'MAIL_HOST' => env('MAIL_HOST'),
|
||||
'MAIL_PORT' => env('MAIL_PORT'),
|
||||
'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION'),
|
||||
'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'),
|
||||
'SES_KEY' => env('SES_KEY'),
|
||||
'SES_SECRET' => env('SES_SECRET'),
|
||||
];
|
||||
$this->createSender($settings, $company->id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function createSender($settings, $company_id)
|
||||
{
|
||||
$data = [
|
||||
'name' => env('MAIL_DRIVER'),
|
||||
'driver' => env('MAIL_DRIVER'),
|
||||
'is_default' => true,
|
||||
'from_address' => env('MAIL_FROM_ADDRESS'),
|
||||
'from_name' => env('MAIL_FROM_NAME'),
|
||||
'settings' => $settings ?? null,
|
||||
'company_id' => $company_id
|
||||
];
|
||||
|
||||
MailSender::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('mail_senders');
|
||||
}
|
||||
}
|
||||
@ -170,7 +170,7 @@ class CountriesTableSeeder extends Seeder
|
||||
['id' => 152,'code' => 'NR','name' => "Nauru",'phonecode' => 674],
|
||||
['id' => 153,'code' => 'NP','name' => "Nepal",'phonecode' => 977],
|
||||
['id' => 154,'code' => 'AN','name' => "Netherlands Antilles",'phonecode' => 599],
|
||||
['id' => 155,'code' => 'NL','name' => "Netherlands The",'phonecode' => 31],
|
||||
['id' => 155,'code' => 'NL','name' => "Netherlands",'phonecode' => 31],
|
||||
['id' => 156,'code' => 'NC','name' => "New Caledonia",'phonecode' => 687],
|
||||
['id' => 157,'code' => 'NZ','name' => "New Zealand",'phonecode' => 64],
|
||||
['id' => 158,'code' => 'NI','name' => "Nicaragua",'phonecode' => 505],
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
FROM php:7.4-fpm-alpine
|
||||
FROM php:8.0-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
php7-bcmath
|
||||
php8-bcmath
|
||||
|
||||
RUN docker-php-ext-install pdo pdo_mysql bcmath
|
||||
|
||||
|
||||
@ -47,8 +47,6 @@ const ExpenseCategory = () =>
|
||||
import('@/scripts/admin/views/settings/ExpenseCategorySetting.vue')
|
||||
const ExchangeRateSetting = () =>
|
||||
import('@/scripts/admin/views/settings/ExchangeRateProviderSetting.vue')
|
||||
const MailConfig = () =>
|
||||
import('@/scripts/admin/views/settings/MailConfigSetting.vue')
|
||||
const FileDisk = () =>
|
||||
import('@/scripts/admin/views/settings/FileDiskSetting.vue')
|
||||
const Backup = () => import('@/scripts/admin/views/settings/BackupSetting.vue')
|
||||
@ -56,6 +54,8 @@ const UpdateApp = () =>
|
||||
import('@/scripts/admin/views/settings/UpdateAppSetting.vue')
|
||||
const RolesSettings = () =>
|
||||
import('@/scripts/admin/views/settings/RolesSettings.vue')
|
||||
const MailSender = () =>
|
||||
import('@/scripts/admin/views/settings/mail-sender/Index.vue')
|
||||
|
||||
// Items
|
||||
const ItemsIndex = () => import('@/scripts/admin/views/items/Index.vue')
|
||||
@ -302,13 +302,6 @@ export default [
|
||||
meta: { ability: abilities.VIEW_EXPENSE },
|
||||
component: ExpenseCategory,
|
||||
},
|
||||
|
||||
{
|
||||
path: 'mail-configuration',
|
||||
name: 'mailconfig',
|
||||
meta: { isOwner: true },
|
||||
component: MailConfig,
|
||||
},
|
||||
{
|
||||
path: 'file-disk',
|
||||
name: 'file-disk',
|
||||
@ -327,6 +320,13 @@ export default [
|
||||
meta: { isOwner: true },
|
||||
component: UpdateApp,
|
||||
},
|
||||
{
|
||||
path: 'mail-sender',
|
||||
name: 'mailsender',
|
||||
meta: { ability: abilities.VIEW_MAIL_SENDER },
|
||||
component: MailSender,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
123
resources/scripts/admin/components/FeedbackAlert.vue
Normal file
123
resources/scripts/admin/components/FeedbackAlert.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<!-- warning alert -->
|
||||
<div
|
||||
v-if="type == 'warning'"
|
||||
class="rounded-md p-4 m-5"
|
||||
:class="{
|
||||
'bg-yellow-50': type == 'warning',
|
||||
'bg-blue-50': type == 'info',
|
||||
'bg-red-50': type == 'error',
|
||||
'bg-green-50': type == 'success',
|
||||
}"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<!-- Heroicon name: solid/exclamation -->
|
||||
<svg
|
||||
v-if="type == 'warning'"
|
||||
class="h-5 w-5 text-yellow-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Heroicon name: solid/information-circle -->
|
||||
<svg
|
||||
v-else-if="type == 'info'"
|
||||
class="h-5 w-5 text-blue-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Heroicon name: solid/x-circle -->
|
||||
<svg
|
||||
v-else-if="type == 'error'"
|
||||
class="h-5 w-5 text-red-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Heroicon name: solid/check-circle -->
|
||||
<svg
|
||||
v-else
|
||||
class="h-5 w-5 text-green-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3
|
||||
class="text-sm font-medium"
|
||||
:class="{
|
||||
'text-yellow-800': type == 'warning',
|
||||
'text-blue-800': type == 'info',
|
||||
'text-red-800': type == 'error',
|
||||
'text-green-800': type == 'success',
|
||||
}"
|
||||
>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div
|
||||
class="text-sm"
|
||||
:class="{
|
||||
'text-yellow-700': type == 'warning',
|
||||
'text-blue-700': type == 'info',
|
||||
'text-red-700': type == 'error',
|
||||
'text-green-700': type == 'success',
|
||||
}"
|
||||
>
|
||||
<p>{{ description }}</p>
|
||||
</div>
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<BaseDropdown>
|
||||
<template #activator>
|
||||
<BaseButton v-if="route.name === 'mailsender.view'" variant="primary">
|
||||
<BaseIcon name="DotsHorizontalIcon" class="h-5 text-white" />
|
||||
</BaseButton>
|
||||
<BaseIcon v-else name="DotsHorizontalIcon" class="h-5 text-gray-500" />
|
||||
</template>
|
||||
|
||||
<!-- edit mail-sender -->
|
||||
<BaseDropdownItem @click="editMailSender(row.id)">
|
||||
<BaseIcon
|
||||
name="PencilIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
{{ $t('general.edit') }}
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- send test mail-sender -->
|
||||
<BaseDropdownItem @click="openMailSenderTestModal(row.id)">
|
||||
<BaseIcon
|
||||
name="PaperAirplaneIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
{{ $t('general.send_test_mail') }}
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- delete mail-sender -->
|
||||
<BaseDropdownItem v-if="!row.is_default" @click="removeMailSender(row.id)">
|
||||
<BaseIcon
|
||||
name="TrashIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
{{ $t('general.delete') }}
|
||||
</BaseDropdownItem>
|
||||
</BaseDropdown>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDialogStore } from '@/scripts/stores/dialog'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { inject } from 'vue'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
table: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
loadData: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const dialogStore = useDialogStore()
|
||||
const { t } = useI18n()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const route = useRoute()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
async function editMailSender(id) {
|
||||
await mailSenderStore.fetchMailSender(id)
|
||||
modalStore.openModal({
|
||||
title: t(`${pre_t}.edit_mail_sender`),
|
||||
componentName: 'MailSenderModal',
|
||||
size: 'md',
|
||||
refreshData: props.loadData && props.loadData,
|
||||
})
|
||||
}
|
||||
|
||||
function removeMailSender(id) {
|
||||
dialogStore
|
||||
.openDialog({
|
||||
title: t('general.are_you_sure'),
|
||||
message: t(`${pre_t}.confirm_delete`),
|
||||
yesLabel: t('general.ok'),
|
||||
noLabel: t('general.cancel'),
|
||||
variant: 'danger',
|
||||
hideNoButton: false,
|
||||
size: 'lg',
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (res) {
|
||||
let response = await mailSenderStore.deleteMailSender(id)
|
||||
if (response.data.success) {
|
||||
props.loadData && props.loadData()
|
||||
return true
|
||||
}
|
||||
props.loadData && props.loadData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function openMailSenderTestModal(id) {
|
||||
modalStore.openModal({
|
||||
title: t(`general.send_test_mail`),
|
||||
componentName: 'MailSenderTestModal',
|
||||
size: 'md',
|
||||
id: id,
|
||||
refreshData: props.loadData && props.loadData,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -17,18 +17,7 @@
|
||||
<td class="px-5 py-4 text-left align-top">
|
||||
<div class="flex justify-start">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
w-5
|
||||
h-5
|
||||
mt-2
|
||||
text-gray-300
|
||||
cursor-move
|
||||
handle
|
||||
mr-2
|
||||
"
|
||||
class="flex items-center justify-center w-5 h-5 mt-2 mr-2 text-gray-300 cursor-move handle"
|
||||
>
|
||||
<DragIcon />
|
||||
</div>
|
||||
@ -108,7 +97,7 @@
|
||||
|
||||
<BaseIcon
|
||||
name="ChevronDownIcon"
|
||||
class="w-4 h-4 text-gray-500 ml-1"
|
||||
class="w-4 h-4 ml-1 text-gray-500"
|
||||
/>
|
||||
</span>
|
||||
</BaseButton>
|
||||
@ -155,7 +144,7 @@
|
||||
<BaseContentPlaceholders v-if="loading">
|
||||
<BaseContentPlaceholdersText
|
||||
:lines="1"
|
||||
class="w-24 h-8 rounded-md border"
|
||||
class="w-24 h-8 border rounded-md"
|
||||
/>
|
||||
</BaseContentPlaceholders>
|
||||
|
||||
@ -175,6 +164,7 @@
|
||||
:ability="abilities.CREATE_INVOICE"
|
||||
:store="store"
|
||||
:store-prop="storeProp"
|
||||
:discount="discount"
|
||||
@update="updateTax"
|
||||
/>
|
||||
</td>
|
||||
|
||||
@ -30,24 +30,13 @@
|
||||
<template v-if="userStore.hasAbilities(ability)" #action>
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
w-full
|
||||
px-2
|
||||
cursor-pointer
|
||||
py-2
|
||||
bg-gray-200
|
||||
border-none
|
||||
outline-none
|
||||
"
|
||||
class="flex items-center justify-center w-full px-2 py-2 bg-gray-200 border-none outline-none cursor-pointer "
|
||||
@click="openTaxModal"
|
||||
>
|
||||
<BaseIcon name="CheckCircleIcon" class="h-5 text-primary-400" />
|
||||
|
||||
<label
|
||||
class="ml-2 text-sm leading-none text-primary-400 cursor-pointer"
|
||||
class="ml-2 text-sm leading-none cursor-pointer text-primary-400"
|
||||
>{{ $t('invoices.add_new_tax') }}</label
|
||||
>
|
||||
</button>
|
||||
@ -115,6 +104,10 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
discountedTotal: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
currency: {
|
||||
type: [Object, String],
|
||||
required: true,
|
||||
@ -153,19 +146,19 @@ const filteredTypes = computed(() => {
|
||||
})
|
||||
|
||||
const taxAmount = computed(() => {
|
||||
if (localTax.compound_tax && props.total) {
|
||||
return ((props.total + props.totalTax) * localTax.percent) / 100
|
||||
if (localTax.compound_tax && props.discountedTotal) {
|
||||
return ((props.discountedTotal + props.totalTax) * localTax.percent) / 100
|
||||
}
|
||||
|
||||
if (props.total && localTax.percent) {
|
||||
return (props.total * localTax.percent) / 100
|
||||
if (props.discountedTotal && localTax.percent) {
|
||||
return (props.discountedTotal * localTax.percent) / 100
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.total,
|
||||
() => props.discountedTotal,
|
||||
() => {
|
||||
updateRowTax()
|
||||
}
|
||||
|
||||
@ -29,14 +29,7 @@
|
||||
|
||||
<label
|
||||
v-else
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
m-0
|
||||
text-lg text-black
|
||||
uppercase
|
||||
"
|
||||
class="flex items-center justify-center m-0 text-lg text-black uppercase "
|
||||
>
|
||||
<BaseFormatMoney
|
||||
:amount="store.getSubTotal"
|
||||
@ -66,14 +59,7 @@
|
||||
|
||||
<label
|
||||
v-else-if="store[storeProp].tax_per_item === 'YES'"
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
m-0
|
||||
text-lg text-black
|
||||
uppercase
|
||||
"
|
||||
class="flex items-center justify-center m-0 text-lg text-black uppercase "
|
||||
>
|
||||
<BaseFormatMoney :amount="tax.amount" :currency="defaultCurrency" />
|
||||
</label>
|
||||
@ -98,7 +84,7 @@
|
||||
<BaseContentPlaceholders v-if="isLoading">
|
||||
<BaseContentPlaceholdersText
|
||||
:lines="1"
|
||||
class="w-24 h-8 rounded-md border"
|
||||
class="w-24 h-8 border rounded-md"
|
||||
/>
|
||||
</BaseContentPlaceholders>
|
||||
<div v-else class="flex" style="width: 140px" role="group">
|
||||
@ -114,7 +100,7 @@
|
||||
<BaseDropdown position="bottom-end">
|
||||
<template #activator>
|
||||
<BaseButton
|
||||
class="rounded-tr-md rounded-br-md p-2 rounded-none"
|
||||
class="p-2 rounded-none rounded-tr-md rounded-br-md"
|
||||
type="button"
|
||||
variant="white"
|
||||
>
|
||||
@ -127,7 +113,7 @@
|
||||
|
||||
<BaseIcon
|
||||
name="ChevronDownIcon"
|
||||
class="w-4 h-4 text-gray-500 ml-1"
|
||||
class="w-4 h-4 ml-1 text-gray-500"
|
||||
/>
|
||||
</span>
|
||||
</BaseButton>
|
||||
@ -180,15 +166,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
w-full
|
||||
pt-2
|
||||
mt-5
|
||||
border-t border-gray-200 border-solid
|
||||
"
|
||||
class="flex items-center justify-between w-full pt-2 mt-5 border-t border-gray-200 border-solid "
|
||||
>
|
||||
<BaseContentPlaceholders v-if="isLoading">
|
||||
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
||||
@ -204,14 +182,7 @@
|
||||
</BaseContentPlaceholders>
|
||||
<label
|
||||
v-else
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
text-lg
|
||||
uppercase
|
||||
text-primary-400
|
||||
"
|
||||
class="flex items-center justify-center text-lg uppercase text-primary-400"
|
||||
>
|
||||
<BaseFormatMoney :amount="store.getTotal" :currency="defaultCurrency" />
|
||||
</label>
|
||||
@ -334,6 +305,7 @@ function selectPercentage() {
|
||||
|
||||
function onSelectTax(selectedTax) {
|
||||
let amount = 0
|
||||
|
||||
if (selectedTax.compound_tax && props.store.getSubtotalWithDiscount) {
|
||||
amount = Math.round(
|
||||
((props.store.getSubtotalWithDiscount + props.store.getTotalSimpleTax) *
|
||||
|
||||
@ -453,7 +453,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -549,7 +549,6 @@ const rules = computed(() => {
|
||||
website: {
|
||||
url: helpers.withMessage(t('validation.invalid_url'), url),
|
||||
},
|
||||
|
||||
billing: {
|
||||
address_street_1: {
|
||||
maxLength: helpers.withMessage(
|
||||
|
||||
@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<BaseModal
|
||||
:show="modalStore.active && modalStore.componentName === 'MailSenderModal'"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex justify-between w-full">
|
||||
{{ modalStore.title }}
|
||||
<BaseIcon
|
||||
name="XIcon"
|
||||
class="h-6 w-6 text-gray-500 cursor-pointer"
|
||||
@click="closeMailSenderModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<form action="" @submit.prevent="submitMailSenderData">
|
||||
<div class="p-4 sm:p-6 my-2">
|
||||
<!-- Name -->
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.name`)"
|
||||
:error="v$.name.$error && v$.name.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.name_help`)"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.currentMailSender.name"
|
||||
:invalid="v$.name.$error"
|
||||
type="text"
|
||||
@input="v$.name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- From Name -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.from_name`)"
|
||||
:error="v$.from_name.$error && v$.from_name.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.from_name"
|
||||
:invalid="v$.from_name.$error"
|
||||
type="text"
|
||||
@input="v$.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- From Address -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.from_address`)"
|
||||
:error="
|
||||
v$.from_address.$error && v$.from_address.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.from_address"
|
||||
:invalid="v$.from_address.$error"
|
||||
type="text"
|
||||
@input="v$.from_address.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- CC -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.cc`)"
|
||||
:error="v$.cc.$error && v$.cc.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.email_list`)"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.cc"
|
||||
:invalid="v$.cc.$error"
|
||||
type="text"
|
||||
@input="v$.cc.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- BCC -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.bcc`)"
|
||||
:error="v$.bcc.$error && v$.bcc.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.email_list`)"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.bcc"
|
||||
:invalid="v$.bcc.$error"
|
||||
type="text"
|
||||
@input="v$.bcc.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Mail Driver -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.driver`)"
|
||||
:error="v$.driver.$error && v$.driver.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailSenderStore.currentMailSender.driver"
|
||||
:options="mailSenderStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.driver.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<component
|
||||
:is="loadMailDriver"
|
||||
:mail-sender-store="mailSenderStore"
|
||||
/>
|
||||
</BaseInputGrid>
|
||||
|
||||
<BaseDivider class="mt-4 mb-0" />
|
||||
|
||||
<!-- Is Default? -->
|
||||
<BaseSwitchSection
|
||||
v-if="!mailSenderStore.isDisable"
|
||||
v-model="mailSenderStore.currentMailSender.is_default"
|
||||
:title="$t(`${pre_t}.is_default`)"
|
||||
:description="$t(`${pre_t}.is_default_description`)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-solid border--200 border-modal-bg"
|
||||
>
|
||||
<BaseButton
|
||||
class="mr-3 text-sm"
|
||||
variant="primary-outline"
|
||||
type="button"
|
||||
@click="closeMailSenderModal"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!isSaving"
|
||||
name="SaveIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
</template>
|
||||
{{
|
||||
mailSenderStore.isEdit ? $t('general.update') : $t('general.save')
|
||||
}}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { required, email, minLength, helpers } from '@vuelidate/validators'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import SmtpDriver from '@/scripts/admin/views/settings/mail-sender/SmtpDriver.vue'
|
||||
import MailgunDriver from '@/scripts/admin/views/settings/mail-sender/MailgunDriver.vue'
|
||||
import SesDriver from '@/scripts/admin/views/settings/mail-sender/SesDriver.vue'
|
||||
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const modalStore = useModalStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const { t } = useI18n()
|
||||
let isSaving = ref(false)
|
||||
|
||||
const loadMailDriver = computed(() => {
|
||||
switch (mailSenderStore.currentMailSender.driver) {
|
||||
case 'smtp':
|
||||
return SmtpDriver
|
||||
case 'mail':
|
||||
return false
|
||||
case 'sendmail':
|
||||
return false
|
||||
case 'mailgun':
|
||||
return MailgunDriver
|
||||
case 'ses':
|
||||
return SesDriver
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// This is multiple email custom validation
|
||||
const multiEmail = (value) => {
|
||||
if (value == '' || value === null) return true
|
||||
const emailRegex =
|
||||
/^(?:[A-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]{2,}(?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i
|
||||
|
||||
const emailArr = value.split(',')
|
||||
let isValid = emailArr.every((v) => {
|
||||
return emailRegex.test(v)
|
||||
})
|
||||
return isValid
|
||||
}
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
minLength: helpers.withMessage(
|
||||
t('validation.name_min_length', { count: 3 }),
|
||||
minLength(3)
|
||||
),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_address: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
cc: {
|
||||
multiEmail: helpers.withMessage(
|
||||
t('validation.email_incorrect'),
|
||||
multiEmail
|
||||
),
|
||||
},
|
||||
bcc: {
|
||||
multiEmail: helpers.withMessage(
|
||||
t('validation.email_incorrect'),
|
||||
multiEmail
|
||||
),
|
||||
},
|
||||
driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailSenderStore.currentMailSender)
|
||||
)
|
||||
|
||||
async function submitMailSenderData() {
|
||||
v$.value.$touch()
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
const action = mailSenderStore.isEdit
|
||||
? mailSenderStore.updateMailSender
|
||||
: mailSenderStore.addMailSender
|
||||
isSaving.value = true
|
||||
|
||||
var mailDriverConfig = null
|
||||
switch (mailSenderStore.currentMailSender.driver) {
|
||||
case 'smtp':
|
||||
mailDriverConfig = mailSenderStore.smtpConfig
|
||||
break
|
||||
case 'mailgun':
|
||||
mailDriverConfig = mailSenderStore.mailgunConfig
|
||||
break
|
||||
case 'ses':
|
||||
mailDriverConfig = mailSenderStore.sesConfig
|
||||
break
|
||||
}
|
||||
mailSenderStore.currentMailSender.settings = mailDriverConfig
|
||||
|
||||
let res = await action(mailSenderStore.currentMailSender)
|
||||
isSaving.value = false
|
||||
modalStore.refreshData ? modalStore.refreshData(res.data.data) : ''
|
||||
closeMailSenderModal()
|
||||
} catch (err) {
|
||||
isSaving.value = false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function closeMailSenderModal() {
|
||||
modalStore.closeModal()
|
||||
setTimeout(() => {
|
||||
mailSenderStore.resetCurrentMailSender()
|
||||
v$.value.$reset()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await mailSenderStore.fetchMailDrivers()
|
||||
})
|
||||
</script>
|
||||
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<BaseModal :show="modalActive" @open="setInitialData">
|
||||
<template #header>
|
||||
<div class="flex justify-between w-full">
|
||||
{{ modalStore.title }}
|
||||
<BaseIcon
|
||||
name="XIcon"
|
||||
class="w-6 h-6 text-gray-500 cursor-pointer"
|
||||
@click="closeTestModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<form action="" @submit.prevent="onTestMailSend">
|
||||
<div class="p-4 md:p-8">
|
||||
<BaseInputGrid layout="one-column">
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.title`)"
|
||||
variant="horizontal"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mail_sender_id.$error && v$.mail_sender_id.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="formData.mail_sender_id"
|
||||
:invalid="v$.mail_sender_id.$error"
|
||||
label="name"
|
||||
:options="mailSenderStore.mailSenders"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:placeholder="$t(`${pre_t}.select_mail_sender`)"
|
||||
searchable
|
||||
track-by="name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('general.to')"
|
||||
:error="v$.to.$error && v$.to.$errors[0].$message"
|
||||
variant="horizontal"
|
||||
required
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="formData.to"
|
||||
type="text"
|
||||
:invalid="v$.to.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.to.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
<BaseInputGroup
|
||||
:label="$t('general.subject')"
|
||||
:error="v$.subject.$error && v$.subject.$errors[0].$message"
|
||||
variant="horizontal"
|
||||
required
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="formData.subject"
|
||||
type="text"
|
||||
:invalid="v$.subject.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.subject.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
<BaseInputGroup
|
||||
:label="$t('general.message')"
|
||||
:error="v$.message.$error && v$.message.$errors[0].$message"
|
||||
variant="horizontal"
|
||||
required
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<BaseTextarea
|
||||
v-model="formData.message"
|
||||
rows="4"
|
||||
cols="50"
|
||||
:invalid="v$.message.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.message.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid"
|
||||
>
|
||||
<BaseButton
|
||||
variant="primary-outline"
|
||||
type="button"
|
||||
class="mr-3"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@click="closeTestModal()"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!isSaving"
|
||||
name="PaperAirplaneIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
</template>
|
||||
{{ $t('general.send') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { required, email, maxLength, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const modalStore = useModalStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const { t } = useI18n()
|
||||
let isSaving = ref(false)
|
||||
let formData = reactive({
|
||||
mail_sender_id: '',
|
||||
to: '',
|
||||
subject: '',
|
||||
message: '',
|
||||
})
|
||||
const isFetchingInitialData = ref(false)
|
||||
|
||||
const modalActive = computed(() => {
|
||||
return modalStore.active && modalStore.componentName === 'MailSenderTestModal'
|
||||
})
|
||||
|
||||
function setInitialData() {
|
||||
isFetchingInitialData.value = true
|
||||
formData.mail_sender_id = modalStore.id
|
||||
setTimeout(() => {
|
||||
isFetchingInitialData.value = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const rules = {
|
||||
mail_sender_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
to: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
subject: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.subject_maxlength'),
|
||||
maxLength(100)
|
||||
),
|
||||
},
|
||||
message: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.message_maxlength'),
|
||||
maxLength(255)
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(rules, formData)
|
||||
|
||||
function resetFormData() {
|
||||
formData.mail_sender_id = ''
|
||||
formData.to = ''
|
||||
formData.subject = ''
|
||||
formData.message = ''
|
||||
|
||||
v$.value.$reset()
|
||||
}
|
||||
|
||||
async function onTestMailSend() {
|
||||
v$.value.$touch()
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
let response = await mailSenderStore.sendTestMail(formData)
|
||||
if (response.data) {
|
||||
closeTestModal()
|
||||
}
|
||||
}
|
||||
function closeTestModal() {
|
||||
modalStore.closeModal()
|
||||
setTimeout(() => {
|
||||
isSaving.value = false
|
||||
modalStore.resetModalData()
|
||||
resetFormData()
|
||||
}, 300)
|
||||
}
|
||||
</script>
|
||||
@ -16,18 +16,28 @@
|
||||
</template>
|
||||
|
||||
<form v-if="!isPreview" action="">
|
||||
<div class="px-8 py-8 sm:p-6">
|
||||
<!-- v-if -->
|
||||
<div v-if="isMailSenderExist" class="px-8 py-8 sm:p-6">
|
||||
<BaseInputGrid layout="one-column">
|
||||
<BaseInputGroup
|
||||
:label="$t('general.from')"
|
||||
:label="$tc('settings.mail_sender.title', 1)"
|
||||
required
|
||||
:error="v$.from.$error && v$.from.$errors[0].$message"
|
||||
:error="
|
||||
v$.mail_sender_id.$error && v$.mail_sender_id.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="estimateMailForm.from"
|
||||
type="text"
|
||||
:invalid="v$.from.$error"
|
||||
@input="v$.from.$touch()"
|
||||
<BaseMultiselect
|
||||
v-model="estimateMailForm.mail_sender_id"
|
||||
:invalid="v$.mail_sender_id.$error"
|
||||
label="name"
|
||||
:options="mailSenders"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:placeholder="$t(`settings.mail_sender.select_mail_sender`)"
|
||||
searchable
|
||||
track-by="name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
<BaseInputGroup
|
||||
@ -62,6 +72,45 @@
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
<!-- v-else -->
|
||||
<div v-else-if="!isMailSenderExist && !isFetchingInitialData">
|
||||
<FeedbackAlert
|
||||
:title="$t('settings.mail_sender.no_mail_sender_found')"
|
||||
:description="
|
||||
$t('settings.mail_sender.no_mail_sender_found_description')
|
||||
"
|
||||
type="warning"
|
||||
>
|
||||
<template #action>
|
||||
<div class="mt-4">
|
||||
<div class="-mx-2 -my-1.5 flex">
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
bg-yellow-50
|
||||
px-2
|
||||
py-1.5
|
||||
rounded-md
|
||||
text-sm
|
||||
font-medium
|
||||
text-yellow-800
|
||||
hover:bg-yellow-100
|
||||
focus:outline-none
|
||||
focus:ring-2
|
||||
focus:ring-offset-2
|
||||
focus:ring-offset-yellow-50
|
||||
focus:ring-yellow-600
|
||||
"
|
||||
@click="gotoMailSender"
|
||||
>
|
||||
{{ $t('settings.mail_sender.manage_mail_sender') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FeedbackAlert>
|
||||
</div>
|
||||
<!-- end v-if-else -->
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid"
|
||||
>
|
||||
@ -75,6 +124,7 @@
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="isMailSenderExist"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
@ -141,18 +191,24 @@ import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { useEstimateStore } from '@/scripts/admin/stores/estimate'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import FeedbackAlert from '@/scripts/admin/components/FeedbackAlert.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const modalStore = useModalStore()
|
||||
const estimateStore = useEstimateStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const router = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
const isLoading = ref(false)
|
||||
const templateUrl = ref('')
|
||||
const isPreview = ref(false)
|
||||
const mailSenders = ref(null)
|
||||
const isFetchingInitialData = ref(false)
|
||||
const emailTemplates = ref(null)
|
||||
|
||||
const estimateMailFields = ref([
|
||||
'customer',
|
||||
@ -164,7 +220,7 @@ const estimateMailFields = ref([
|
||||
|
||||
let estimateMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
mail_sender_id: null,
|
||||
to: null,
|
||||
subject: 'New Estimate',
|
||||
body: null,
|
||||
@ -181,9 +237,8 @@ const modalData = computed(() => {
|
||||
})
|
||||
|
||||
const rules = {
|
||||
from: {
|
||||
mail_sender_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
to: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
@ -207,20 +262,26 @@ function cancelPreview() {
|
||||
}
|
||||
|
||||
async function setInitialData() {
|
||||
let admin = await companyStore.fetchBasicMailConfig()
|
||||
|
||||
estimateMailForm.id = modalStore.id
|
||||
|
||||
if (admin.data) {
|
||||
estimateMailForm.from = admin.data.from_mail
|
||||
}
|
||||
|
||||
if (modalData.value) {
|
||||
estimateMailForm.to = modalData.value.customer.email
|
||||
}
|
||||
|
||||
estimateMailForm.body =
|
||||
companyStore.selectedCompanySettings.estimate_mail_body
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' })
|
||||
if (mailSenderData.data) {
|
||||
mailSenders.value = mailSenderData.data.data
|
||||
let defaultMailSender = mailSenderData.data.data.find(
|
||||
(mailSender) => mailSender.is_default == true
|
||||
)
|
||||
estimateMailForm.mail_sender_id = defaultMailSender
|
||||
? defaultMailSender.id
|
||||
: null
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
@ -274,4 +335,18 @@ function closeSendEstimateModal() {
|
||||
templateUrl.value = null
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function getTickImage() {
|
||||
const imgUrl = new URL('/img/tick.png', import.meta.url)
|
||||
return imgUrl
|
||||
}
|
||||
|
||||
const isMailSenderExist = computed(() => {
|
||||
return mailSenders.value && mailSenders.value.length
|
||||
})
|
||||
|
||||
function gotoMailSender() {
|
||||
closeSendEstimateModal()
|
||||
router.push('/admin/settings/mail-sender')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -15,18 +15,28 @@
|
||||
</div>
|
||||
</template>
|
||||
<form v-if="!isPreview" action="">
|
||||
<div class="px-8 py-8 sm:p-6">
|
||||
<!-- v-if -->
|
||||
<div v-if="isMailSenderExist" class="px-8 py-8 sm:p-6">
|
||||
<BaseInputGrid layout="one-column" class="col-span-7">
|
||||
<BaseInputGroup
|
||||
:label="$t('general.from')"
|
||||
:label="$tc('settings.mail_sender.title', 1)"
|
||||
required
|
||||
:error="v$.from.$error && v$.from.$errors[0].$message"
|
||||
:error="
|
||||
v$.mail_sender_id.$error && v$.mail_sender_id.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="invoiceMailForm.from"
|
||||
type="text"
|
||||
:invalid="v$.from.$error"
|
||||
@input="v$.from.$touch()"
|
||||
<BaseMultiselect
|
||||
v-model="invoiceMailForm.mail_sender_id"
|
||||
:invalid="v$.mail_sender_id.$error"
|
||||
label="name"
|
||||
:options="mailSenders"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:placeholder="$t(`settings.mail_sender.select_mail_sender`)"
|
||||
searchable
|
||||
track-by="name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
<BaseInputGroup
|
||||
@ -65,6 +75,45 @@
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
<!-- v-else -->
|
||||
<div v-else-if="!isMailSenderExist && !isFetchingInitialData">
|
||||
<FeedbackAlert
|
||||
:title="$t('settings.mail_sender.no_mail_sender_found')"
|
||||
:description="
|
||||
$t('settings.mail_sender.no_mail_sender_found_description')
|
||||
"
|
||||
type="warning"
|
||||
>
|
||||
<template #action>
|
||||
<div class="mt-4">
|
||||
<div class="-mx-2 -my-1.5 flex">
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
bg-yellow-50
|
||||
px-2
|
||||
py-1.5
|
||||
rounded-md
|
||||
text-sm
|
||||
font-medium
|
||||
text-yellow-800
|
||||
hover:bg-yellow-100
|
||||
focus:outline-none
|
||||
focus:ring-2
|
||||
focus:ring-offset-2
|
||||
focus:ring-offset-yellow-50
|
||||
focus:ring-yellow-600
|
||||
"
|
||||
@click="gotoMailSender"
|
||||
>
|
||||
{{ $t('settings.mail_sender.manage_mail_sender') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FeedbackAlert>
|
||||
</div>
|
||||
<!-- end v-if-else -->
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid"
|
||||
>
|
||||
@ -77,6 +126,7 @@
|
||||
{{ $t('general.cancel') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="isMailSenderExist"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
@ -154,18 +204,24 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useInvoiceStore } from '@/scripts/admin/stores/invoice'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
import { required, email, helpers } from '@vuelidate/validators'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import FeedbackAlert from '@/scripts/admin/components/FeedbackAlert.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const modalStore = useModalStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const router = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
let isLoading = ref(false)
|
||||
const templateUrl = ref('')
|
||||
const isPreview = ref(false)
|
||||
const mailSenders = ref(null)
|
||||
const isFetchingInitialData = ref(false)
|
||||
const emailTemplates = ref(null)
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
|
||||
@ -179,7 +235,7 @@ const invoiceMailFields = ref([
|
||||
|
||||
const invoiceMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
mail_sender_id: null,
|
||||
to: null,
|
||||
subject: 'New Invoice',
|
||||
body: null,
|
||||
@ -198,9 +254,8 @@ const modalData = computed(() => {
|
||||
})
|
||||
|
||||
const rules = {
|
||||
from: {
|
||||
mail_sender_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
to: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
@ -224,19 +279,25 @@ function cancelPreview() {
|
||||
}
|
||||
|
||||
async function setInitialData() {
|
||||
let admin = await companyStore.fetchBasicMailConfig()
|
||||
|
||||
invoiceMailForm.id = modalStore.id
|
||||
|
||||
if (admin.data) {
|
||||
invoiceMailForm.from = admin.data.from_mail
|
||||
}
|
||||
|
||||
if (modalData.value) {
|
||||
invoiceMailForm.to = modalData.value.customer.email
|
||||
}
|
||||
|
||||
invoiceMailForm.body = companyStore.selectedCompanySettings.invoice_mail_body
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' })
|
||||
if (mailSenderData.data) {
|
||||
mailSenders.value = mailSenderData.data.data
|
||||
let defaultMailSender = mailSenderData.data.data.find(
|
||||
(mailSender) => mailSender.is_default == true
|
||||
)
|
||||
invoiceMailForm.mail_sender_id = defaultMailSender
|
||||
? defaultMailSender.id
|
||||
: null
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
@ -287,4 +348,18 @@ function closeSendInvoiceModal() {
|
||||
templateUrl.value = null
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function getTickImage() {
|
||||
const imgUrl = new URL('/img/tick.png', import.meta.url)
|
||||
return imgUrl
|
||||
}
|
||||
|
||||
const isMailSenderExist = computed(() => {
|
||||
return mailSenders.value && mailSenders.value.length
|
||||
})
|
||||
|
||||
function gotoMailSender() {
|
||||
closeSendInvoiceModal()
|
||||
router.push('/admin/settings/mail-sender')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -15,18 +15,28 @@
|
||||
</div>
|
||||
</template>
|
||||
<form v-if="!isPreview" action="">
|
||||
<div class="px-8 py-8 sm:p-6">
|
||||
<!-- v-if -->
|
||||
<div v-if="isMailSenderExist" class="px-8 py-8 sm:p-6">
|
||||
<BaseInputGrid layout="one-column" class="col-span-7">
|
||||
<BaseInputGroup
|
||||
:label="$t('general.from')"
|
||||
:label="$tc('settings.mail_sender.title', 1)"
|
||||
required
|
||||
:error="v$.from.$error && v$.from.$errors[0].$message"
|
||||
:error="
|
||||
v$.mail_sender_id.$error && v$.mail_sender_id.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="paymentMailForm.from"
|
||||
type="text"
|
||||
:invalid="v$.from.$error"
|
||||
@input="v$.from.$touch()"
|
||||
<BaseMultiselect
|
||||
v-model="paymentMailForm.mail_sender_id"
|
||||
:invalid="v$.mail_sender_id.$error"
|
||||
label="name"
|
||||
:options="mailSenders"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:placeholder="$t(`settings.mail_sender.select_mail_sender`)"
|
||||
searchable
|
||||
track-by="name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
<BaseInputGroup
|
||||
@ -65,6 +75,45 @@
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
<!-- v-else -->
|
||||
<div v-else-if="!isMailSenderExist && !isFetchingInitialData">
|
||||
<FeedbackAlert
|
||||
:title="$t('settings.mail_sender.no_mail_sender_found')"
|
||||
:description="
|
||||
$t('settings.mail_sender.no_mail_sender_found_description')
|
||||
"
|
||||
type="warning"
|
||||
>
|
||||
<template #action>
|
||||
<div class="mt-4">
|
||||
<div class="-mx-2 -my-1.5 flex">
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
bg-yellow-50
|
||||
px-2
|
||||
py-1.5
|
||||
rounded-md
|
||||
text-sm
|
||||
font-medium
|
||||
text-yellow-800
|
||||
hover:bg-yellow-100
|
||||
focus:outline-none
|
||||
focus:ring-2
|
||||
focus:ring-offset-2
|
||||
focus:ring-offset-yellow-50
|
||||
focus:ring-yellow-600
|
||||
"
|
||||
@click="gotoMailSender"
|
||||
>
|
||||
{{ $t('settings.mail_sender.manage_mail_sender') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FeedbackAlert>
|
||||
</div>
|
||||
<!-- end v-if-else -->
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid"
|
||||
>
|
||||
@ -77,6 +126,7 @@
|
||||
{{ $t('general.cancel') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="isMailSenderExist"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
@ -154,20 +204,26 @@ import { usePaymentStore } from '@/scripts/admin/stores/payment'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { useDialogStore } from '@/scripts/stores/dialog'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import FeedbackAlert from '@/scripts/admin/components/FeedbackAlert.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const paymentStore = usePaymentStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const modalStore = useModalStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const mailDriversStore = useMailDriverStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const router = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
let isLoading = ref(false)
|
||||
const templateUrl = ref('')
|
||||
const isPreview = ref(false)
|
||||
const mailSenders = ref(null)
|
||||
const isFetchingInitialData = ref(false)
|
||||
const emailTemplates = ref(null)
|
||||
|
||||
const paymentMailFields = ref([
|
||||
'customer',
|
||||
@ -179,7 +235,7 @@ const paymentMailFields = ref([
|
||||
|
||||
const paymentMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
mail_sender_id: null,
|
||||
to: null,
|
||||
subject: 'New Payment',
|
||||
body: null,
|
||||
@ -198,9 +254,8 @@ const modalData = computed(() => {
|
||||
})
|
||||
|
||||
const rules = {
|
||||
from: {
|
||||
mail_sender_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
to: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
@ -221,18 +276,25 @@ function cancelPreview() {
|
||||
}
|
||||
|
||||
async function setInitialData() {
|
||||
let admin = await companyStore.fetchBasicMailConfig()
|
||||
paymentMailForm.id = modalStore.id
|
||||
|
||||
if (admin.data) {
|
||||
paymentMailForm.from = admin.data.from_mail
|
||||
}
|
||||
|
||||
if (modalData.value) {
|
||||
paymentMailForm.to = modalData.value.customer.email
|
||||
}
|
||||
|
||||
paymentMailForm.body = companyStore.selectedCompanySettings.payment_mail_body
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' })
|
||||
if (mailSenderData.data) {
|
||||
mailSenders.value = mailSenderData.data.data
|
||||
let defaultMailSender = mailSenderData.data.data.find(
|
||||
(mailSender) => mailSender.is_default == true
|
||||
)
|
||||
paymentMailForm.mail_sender_id = defaultMailSender
|
||||
? defaultMailSender.id
|
||||
: null
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPaymentData() {
|
||||
@ -280,4 +342,18 @@ function closeSendPaymentModal() {
|
||||
modalStore.resetModalData()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function getTickImage() {
|
||||
const imgUrl = new URL('/img/tick.png', import.meta.url)
|
||||
return imgUrl
|
||||
}
|
||||
|
||||
const isMailSenderExist = computed(() => {
|
||||
return mailSenders.value && mailSenders.value.length
|
||||
})
|
||||
|
||||
function gotoMailSender() {
|
||||
closeSendPaymentModal()
|
||||
router.push('/admin/settings/mail-sender')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
<template #activator>
|
||||
<img
|
||||
:src="previewAvatar"
|
||||
class="block w-8 h-8 rounded md:h-9 md:w-9"
|
||||
class="block w-8 h-8 rounded md:h-9 md:w-9 object-cover"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,146 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { handleError } from '@/scripts/helpers/error-handling'
|
||||
|
||||
export const useMailDriverStore = (useWindow = false) => {
|
||||
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
|
||||
const { global } = window.i18n
|
||||
|
||||
return defineStoreFunc({
|
||||
id: 'mail-driver',
|
||||
|
||||
state: () => ({
|
||||
mailConfigData: null,
|
||||
mail_driver: 'smtp',
|
||||
mail_drivers: [],
|
||||
|
||||
basicMailConfig: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
|
||||
mailgunConfig: {
|
||||
mail_driver: '',
|
||||
mail_mailgun_domain: '',
|
||||
mail_mailgun_secret: '',
|
||||
mail_mailgun_endpoint: '',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
|
||||
sesConfig: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_ses_key: '',
|
||||
mail_ses_secret: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
|
||||
smtpConfig: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_username: '',
|
||||
mail_password: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
fetchMailDrivers() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get('/api/v1/mail/drivers')
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
this.mail_drivers = response.data
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
fetchMailConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get('/api/v1/mail/config')
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
this.mailConfigData = response.data
|
||||
this.mail_driver = response.data.mail_driver
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
updateMailConfig(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('/api/v1/mail/config', data)
|
||||
.then((response) => {
|
||||
const notificationStore = useNotificationStore()
|
||||
if (response.data.success) {
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t('wizard.success.' + response.data.success),
|
||||
})
|
||||
} else {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: global.t('wizard.errors.' + response.data.error),
|
||||
})
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
sendTestMail(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('/api/v1/mail/test', data)
|
||||
.then((response) => {
|
||||
const notificationStore = useNotificationStore()
|
||||
if (response.data.success) {
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t('general.send_mail_successfully'),
|
||||
})
|
||||
} else {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: global.t('validation.something_went_wrong'),
|
||||
})
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
})()
|
||||
}
|
||||
202
resources/scripts/admin/stores/mail-sender.js
Normal file
202
resources/scripts/admin/stores/mail-sender.js
Normal file
@ -0,0 +1,202 @@
|
||||
import axios from 'axios'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { handleError } from '@/scripts/helpers/error-handling'
|
||||
import mailSenderStub from '@/scripts/admin/stub/mail-sender.js'
|
||||
|
||||
export const useMailSenderStore = (useWindow = false) => {
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
|
||||
const { global } = window.i18n
|
||||
|
||||
return defineStoreFunc({
|
||||
id: 'mailSender',
|
||||
|
||||
state: () => ({
|
||||
mailSenders: [],
|
||||
mail_drivers: [], // list of mail drivers
|
||||
currentMailSender: { ...mailSenderStub.basicConfig },
|
||||
smtpConfig: { ...mailSenderStub.smtpConfig },
|
||||
mailgunConfig: { ...mailSenderStub.mailgunConfig },
|
||||
sesConfig: { ...mailSenderStub.sesConfig },
|
||||
mail_encryptions: ['none', 'tls', 'ssl', 'starttls'],
|
||||
isDisable: false
|
||||
}),
|
||||
|
||||
getters: {
|
||||
isEdit: (state) => (state.currentMailSender.id ? true : false),
|
||||
},
|
||||
|
||||
actions: {
|
||||
resetCurrentMailSender() {
|
||||
this.currentMailSender = { ...mailSenderStub.basicConfig }
|
||||
this.smtpConfig = { ...mailSenderStub.smtpConfig }
|
||||
this.mailgunConfig = { ...mailSenderStub.mailgunConfig }
|
||||
this.sesConfig = { ...mailSenderStub.sesConfig }
|
||||
this.isDisable = false
|
||||
},
|
||||
|
||||
fetchMailDrivers() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get('/api/v1/mail-drivers')
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
this.mail_drivers = response.data
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
fetchMailSenders(params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(`/api/v1/mail-senders`, { params })
|
||||
.then((response) => {
|
||||
this.mailSenders = response.data.data
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
fetchMailSender(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(`/api/v1/mail-senders/${id}`)
|
||||
.then((response) => {
|
||||
this.currentMailSender = response.data.data
|
||||
this.isDisable = response.data.data.is_default
|
||||
if (response.data.data.settings) {
|
||||
var settings = response.data.data.settings
|
||||
const encryptionNone = settings.encryption == '' || settings.encryption == undefined
|
||||
switch (response.data.data.driver) {
|
||||
case 'smtp':
|
||||
this.smtpConfig = settings
|
||||
encryptionNone ? this.smtpConfig.encryption = 'none' : ''
|
||||
break
|
||||
case 'mailgun':
|
||||
this.mailgunConfig = settings
|
||||
break
|
||||
case 'ses':
|
||||
this.sesConfig = settings
|
||||
encryptionNone ? this.sesConfig.encryption = 'none' : ''
|
||||
break
|
||||
}
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
addMailSender(data) {
|
||||
const notificationStore = useNotificationStore()
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('/api/v1/mail-senders', data)
|
||||
.then((response) => {
|
||||
this.mailSenders.push(response.data.data)
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t(`${pre_t}.created_message`),
|
||||
})
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
updateMailSender(data) {
|
||||
const notificationStore = useNotificationStore()
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.put(`/api/v1/mail-senders/${data.id}`, data)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
let pos = this.mailSenders.findIndex(
|
||||
(mailSender) => mailSender.id === response.data.data.id
|
||||
)
|
||||
this.mailSenders[pos] = data
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t(`${pre_t}.updated_message`),
|
||||
})
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
deleteMailSender(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.delete(`/api/v1/mail-senders/${id}`)
|
||||
.then((response) => {
|
||||
if (response.data.success) {
|
||||
let index = this.mailSenders.findIndex(
|
||||
(mailSender) => mailSender.id === id
|
||||
)
|
||||
this.mailSenders.splice(index, 1)
|
||||
const notificationStore = useNotificationStore()
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t(`${pre_t}.deleted_message`),
|
||||
})
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
sendTestMail(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('/api/v1/mail-test', data)
|
||||
.then((response) => {
|
||||
const notificationStore = useNotificationStore()
|
||||
if (response.data.success) {
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: global.t('general.send_mail_successfully'),
|
||||
})
|
||||
} else {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: global.t('validation.something_went_wrong'),
|
||||
})
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
})()
|
||||
}
|
||||
@ -25,6 +25,7 @@ export const useUsersStore = (useWindow = false) => {
|
||||
password: null,
|
||||
phone: null,
|
||||
companies: [],
|
||||
sender_id: null,
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
@ -64,6 +64,13 @@ export default {
|
||||
EDIT_ROLE: 'edit-role',
|
||||
VIEW_ROLE: 'view-role',
|
||||
|
||||
// Mail Sender
|
||||
CREATE_MAIL_SENDER: 'view-mail-sender',
|
||||
DELETE_MAIL_SENDER: 'delete-mail-sender',
|
||||
EDIT_MAIL_SENDER: 'edit-mail-sender',
|
||||
VIEW_MAIL_SENDER: 'view-mail-sender',
|
||||
|
||||
|
||||
// exchange rates
|
||||
VIEW_EXCHANGE_RATE: 'view-exchange-rate-provider',
|
||||
CREATE_EXCHANGE_RATE: 'create-exchange-rate-provider',
|
||||
|
||||
@ -15,5 +15,6 @@ export default function () {
|
||||
customFields: [],
|
||||
fields: [],
|
||||
enable_portal: false,
|
||||
mail_sender_id: null,
|
||||
}
|
||||
}
|
||||
|
||||
31
resources/scripts/admin/stub/mail-sender.js
Normal file
31
resources/scripts/admin/stub/mail-sender.js
Normal file
@ -0,0 +1,31 @@
|
||||
export default {
|
||||
basicConfig: {
|
||||
name: '',
|
||||
from_name: '',
|
||||
from_address: '',
|
||||
cc: '',
|
||||
bcc: '',
|
||||
is_default: false,
|
||||
driver: 'smtp', // 'smtp', 'mail', 'sendmail', 'mailgun', 'ses'
|
||||
settings: '',
|
||||
},
|
||||
smtpConfig: {
|
||||
host: '',
|
||||
port: null,
|
||||
username: '',
|
||||
password: '',
|
||||
encryption: 'tls', // 'tls', 'ssl', 'starttls'
|
||||
},
|
||||
mailgunConfig: {
|
||||
domain: '',
|
||||
secret: '',
|
||||
endpoint: '',
|
||||
},
|
||||
sesConfig: {
|
||||
host: '',
|
||||
port: null,
|
||||
encryption: 'tls', // 'tls', 'ssl', 'starttls'
|
||||
ses_key: '',
|
||||
ses_secret: '',
|
||||
},
|
||||
}
|
||||
@ -256,6 +256,7 @@
|
||||
/> </template
|
||||
></BaseInput>
|
||||
</BaseInputGroup>
|
||||
<!-- && setPasswordMethod !== 'manual' -->
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
|
||||
@ -650,10 +651,7 @@ const rules = computed(() => {
|
||||
},
|
||||
|
||||
email: {
|
||||
required: helpers.withMessage(
|
||||
t('validation.required'),
|
||||
requiredIf(customerStore.currentCustomer.enable_portal == true)
|
||||
),
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
password: {
|
||||
|
||||
@ -3,75 +3,238 @@
|
||||
:title="$t('wizard.mail.mail_config')"
|
||||
:description="$t('wizard.mail.mail_config_desc')"
|
||||
>
|
||||
<form action="" @submit.prevent="next">
|
||||
<component
|
||||
:is="mailDriverStore.mail_driver"
|
||||
:config-data="mailDriverStore.mailConfigData"
|
||||
:is-saving="isSaving"
|
||||
:is-fetching-initial-data="isFetchingInitialData"
|
||||
@on-change-driver="(val) => changeDriver(val)"
|
||||
@submit-data="next"
|
||||
/>
|
||||
<form action="" @submit.prevent="submitMailSenderData">
|
||||
<div class="p-4 sm:p-6 my-2">
|
||||
<!-- Name -->
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.name`)"
|
||||
:error="v$.name.$error && v$.name.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.name_help`)"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.currentMailSender.name"
|
||||
:invalid="v$.name.$error"
|
||||
type="text"
|
||||
@input="v$.name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- From Name -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.from_name`)"
|
||||
:error="v$.from_name.$error && v$.from_name.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.from_name"
|
||||
:invalid="v$.from_name.$error"
|
||||
type="text"
|
||||
@input="v$.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- From Address -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.from_address`)"
|
||||
:error="
|
||||
v$.from_address.$error && v$.from_address.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.from_address"
|
||||
:invalid="v$.from_address.$error"
|
||||
type="text"
|
||||
@input="v$.from_address.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- CC -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.cc`)"
|
||||
:error="v$.cc.$error && v$.cc.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.email_list`)"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.cc"
|
||||
:invalid="v$.cc.$error"
|
||||
type="text"
|
||||
@input="v$.cc.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- BCC -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.bcc`)"
|
||||
:error="v$.bcc.$error && v$.bcc.$errors[0].$message"
|
||||
:help-text="$t(`${pre_t}.email_list`)"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.currentMailSender.bcc"
|
||||
:invalid="v$.bcc.$error"
|
||||
type="text"
|
||||
@input="v$.bcc.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Mail Driver -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.driver`)"
|
||||
:error="v$.driver.$error && v$.driver.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailSenderStore.currentMailSender.driver"
|
||||
:options="mailSenderStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.driver.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<component
|
||||
:is="loadMailDriver"
|
||||
:mail-sender-store="mailSenderStore"
|
||||
/>
|
||||
</BaseInputGrid>
|
||||
<BaseDivider class="my-4" />
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!isSaving"
|
||||
name="SaveIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Smtp from './mail-driver/SmtpMailDriver.vue'
|
||||
import Mailgun from './mail-driver/MailgunMailDriver.vue'
|
||||
import Ses from './mail-driver/SesMailDriver.vue'
|
||||
import Basic from './mail-driver/BasicMailDriver.vue'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { ref } from 'vue'
|
||||
<script setup>
|
||||
import { computed, ref, inject, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
import { required, email, minLength, helpers } from '@vuelidate/validators'
|
||||
import MailSenderDropdown from '@/scripts/admin/components/dropdowns/MailSenderIndexDropdown.vue'
|
||||
import MailSenderTestModal from '@/scripts/admin/components/modal-components/MailSenderTestModal.vue'
|
||||
import SmtpDriver from '@/scripts/admin/views/settings/mail-sender/SmtpDriver.vue'
|
||||
import MailgunDriver from '@/scripts/admin/views/settings/mail-sender/MailgunDriver.vue'
|
||||
import SesDriver from '@/scripts/admin/views/settings/mail-sender/SesDriver.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Smtp,
|
||||
Mailgun,
|
||||
Ses,
|
||||
sendmail: Basic,
|
||||
Mail: Basic,
|
||||
},
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const { t } = useI18n()
|
||||
const table = ref(null)
|
||||
const utils = inject('utils')
|
||||
let isSaving = ref(false)
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
emits: ['next'],
|
||||
const loadMailDriver = computed(() => {
|
||||
switch (mailSenderStore.currentMailSender.driver) {
|
||||
case 'smtp':
|
||||
return SmtpDriver
|
||||
break
|
||||
case 'mail':
|
||||
return false
|
||||
break
|
||||
case 'sendmail':
|
||||
return false
|
||||
break
|
||||
case 'mailgun':
|
||||
return MailgunDriver
|
||||
break
|
||||
case 'ses':
|
||||
return SesDriver
|
||||
break
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
setup(props, { emit }) {
|
||||
const isSaving = ref(false)
|
||||
const isFetchingInitialData = ref(false)
|
||||
// This is multiple email custom validation
|
||||
const multiEmail = (value) => {
|
||||
if (value == '' || value === null) return true
|
||||
const emailRegex =
|
||||
/^(?:[A-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]{2,}(?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
|
||||
mailDriverStore.mail_driver = 'mail'
|
||||
|
||||
loadData()
|
||||
|
||||
function changeDriver(value) {
|
||||
mailDriverStore.mail_driver = value
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
isFetchingInitialData.value = true
|
||||
await mailDriverStore.fetchMailDrivers()
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
|
||||
async function next(mailConfigData) {
|
||||
isSaving.value = true
|
||||
let res = await mailDriverStore.updateMailConfig(mailConfigData)
|
||||
isSaving.value = false
|
||||
|
||||
if (res.data.success) {
|
||||
await emit('next', 5)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mailDriverStore,
|
||||
isSaving,
|
||||
isFetchingInitialData,
|
||||
changeDriver,
|
||||
next,
|
||||
}
|
||||
},
|
||||
const emailArr = value.split(',')
|
||||
let isValid = emailArr.every((v) => {
|
||||
return emailRegex.test(v)
|
||||
})
|
||||
return isValid
|
||||
}
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
minLength: helpers.withMessage(
|
||||
t('validation.name_min_length', { count: 3 }),
|
||||
minLength(3)
|
||||
),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_address: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
cc: {
|
||||
multiEmail: helpers.withMessage(
|
||||
t('validation.email_incorrect'),
|
||||
multiEmail
|
||||
),
|
||||
},
|
||||
bcc: {
|
||||
multiEmail: helpers.withMessage(
|
||||
t('validation.email_incorrect'),
|
||||
multiEmail
|
||||
),
|
||||
},
|
||||
driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailSenderStore.currentMailSender)
|
||||
)
|
||||
|
||||
async function submitMailSenderData() {
|
||||
v$.value.$touch()
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
let data = {
|
||||
...mailSenderStore.currentMailSender,
|
||||
is_default: true,
|
||||
}
|
||||
await mailSenderStore.addMailSender(data)
|
||||
isSaving.value = true
|
||||
emit('next')
|
||||
isSaving.value = false
|
||||
} catch (err) {
|
||||
isSaving.value = false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await mailSenderStore.fetchMailDrivers()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -54,8 +54,6 @@
|
||||
label="name"
|
||||
:options="itemStore.itemUnits"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:placeholder="$t('items.select_a_unit')"
|
||||
searchable
|
||||
track-by="name"
|
||||
|
||||
@ -1,158 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.mail_driver.$error &&
|
||||
v$.basicMailConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailDriverStore.basicMailConfig.mail_driver"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="mailDrivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.basicMailConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.from_mail.$error &&
|
||||
v$.basicMailConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.basicMailConfig.from_mail"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
:invalid="v$.basicMailConfig.from_mail.$error"
|
||||
@input="v$.basicMailConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.from_name.$error &&
|
||||
v$.basicMailConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.basicMailConfig.from_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="name"
|
||||
:invalid="v$.basicMailConfig.from_name.$error"
|
||||
@input="v$.basicMailConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
<div class="flex mt-8">
|
||||
<BaseButton
|
||||
:content-loading="isFetchingInitialData"
|
||||
:disabled="isSaving"
|
||||
:loading="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" :class="slotProps.class" name="SaveIcon" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, computed } from 'vue'
|
||||
import { required, email, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
basicMailConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in mailDriverStore.basicMailConfig) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
mailDriverStore.$patch((state) => {
|
||||
state.basicMailConfig[key] = props.configData[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.basicMailConfig.$touch()
|
||||
if (!v$.value.basicMailConfig.$invalid) {
|
||||
emit('submit-data', mailDriverStore.basicMailConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.basicMailConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.basicMailConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -1,247 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_driver.$error &&
|
||||
v$.mailgunConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailDriverStore.mailgunConfig.mail_driver"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="mailDrivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.mailgunConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.mailgun_domain')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_domain.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_domain.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.mailgunConfig.mail_mailgun_domain"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mailgun_domain"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_domain.$error"
|
||||
@input="v$.mailgunConfig.mail_mailgun_domain.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.mailgun_secret')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_secret.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_secret.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.mailgunConfig.mail_mailgun_secret"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:type="getInputType"
|
||||
name="mailgun_secret"
|
||||
autocomplete="off"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_secret.$error"
|
||||
@input="v$.mailgunConfig.mail_mailgun_secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.mailgun_endpoint')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_endpoint.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_endpoint.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.mailgunConfig.mail_mailgun_endpoint"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mailgun_endpoint"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_endpoint.$error"
|
||||
@input="v$.mailgunConfig.mail_mailgun_endpoint.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.from_mail.$error &&
|
||||
v$.mailgunConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.mailgunConfig.from_mail"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
:invalid="v$.mailgunConfig.from_mail.$error"
|
||||
@input="v$.mailgunConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.from_name.$error &&
|
||||
v$.mailgunConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.mailgunConfig.from_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_name"
|
||||
:invalid="v$.mailgunConfig.from_name.$error"
|
||||
@input="v$.mailgunConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
<div class="flex my-10">
|
||||
<BaseButton
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:loading="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { required, email, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
mailgunConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_domain: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_endpoint: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in mailDriverStore.mailgunConfig) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
mailDriverStore.mailgunConfig[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.mailgunConfig.$touch()
|
||||
if (!v$.value.mailgunConfig.$invalid) {
|
||||
emit('submit-data', mailDriverStore.mailgunConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.mailgunConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.mailgunConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -1,294 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_driver.$error &&
|
||||
v$.sesConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailDriverStore.sesConfig.mail_driver"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="mailDrivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.sesConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.host')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_host.$error &&
|
||||
v$.sesConfig.mail_host.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.mail_host"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
:invalid="v$.sesConfig.mail_host.$error"
|
||||
@input="v$.sesConfig.mail_host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.port')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_port.$error &&
|
||||
v$.sesConfig.mail_port.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.mail_port"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
:invalid="v$.sesConfig.mail_port.$error"
|
||||
@input="v$.sesConfig.mail_port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.encryption')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_encryption.$error &&
|
||||
v$.sesConfig.mail_encryption.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="mailDriverStore.sesConfig.mail_encryption"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="encryptions"
|
||||
:invalid="v$.sesConfig.mail_encryption.$error"
|
||||
placeholder="Select option"
|
||||
@input="v$.sesConfig.mail_encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.from_mail.$error &&
|
||||
v$.sesConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.from_mail"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
:invalid="v$.sesConfig.from_mail.$error"
|
||||
@input="v$.sesConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.from_name.$error &&
|
||||
v$.sesConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.from_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="name"
|
||||
:invalid="v$.sesConfig.from_name.$error"
|
||||
@input="v$.sesConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.ses_key')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_ses_key.$error &&
|
||||
v$.sesConfig.mail_ses_key.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.mail_ses_key"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_ses_key"
|
||||
:invalid="v$.sesConfig.mail_ses_key.$error"
|
||||
@input="v$.sesConfig.mail_ses_key.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.ses_secret')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_ses_secret.$error &&
|
||||
v$.mail_ses_secret.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.sesConfig.mail_ses_secret"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:type="getInputType"
|
||||
name="mail_ses_secret"
|
||||
autocomplete="off"
|
||||
:invalid="v$.sesConfig.mail_ses_secret.$error"
|
||||
@input="v$.sesConfig.mail_ses_secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
|
||||
<div class="flex my-10">
|
||||
<BaseButton
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:loading="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { required, email, numeric, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
const encryptions = reactive(['tls', 'ssl', 'starttls'])
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
sesConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric,
|
||||
},
|
||||
mail_ses_key: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_ses_secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in mailDriverStore.sesConfig) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
mailDriverStore.sesConfig[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.sesConfig.$touch()
|
||||
if (!v$.value.sesConfig.$invalid) {
|
||||
emit('submit-data', mailDriverStore.sesConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.sesConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.sesConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -1,275 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_driver.$error &&
|
||||
v$.smtpConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailDriverStore.smtpConfig.mail_driver"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="mailDrivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.smtpConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.host')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_host.$error &&
|
||||
v$.smtpConfig.mail_host.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.mail_host"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
:invalid="v$.smtpConfig.mail_host.$error"
|
||||
@input="v$.smtpConfig.mail_host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:content-loading="isFetchingInitialData"
|
||||
:label="$t('settings.mail.username')"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.mail_username"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="db_name"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:content-loading="isFetchingInitialData"
|
||||
:label="$t('settings.mail.password')"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.mail_password"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:type="getInputType"
|
||||
name="password"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.port')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_port.$error &&
|
||||
v$.smtpConfig.mail_port.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.mail_port"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
:invalid="v$.smtpConfig.mail_port.$error"
|
||||
@input="v$.smtpConfig.mail_port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.encryption')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_encryption.$error &&
|
||||
v$.smtpConfig.mail_encryption.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="mailDriverStore.smtpConfig.mail_encryption"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
placeholder="Select option"
|
||||
:invalid="v$.smtpConfig.mail_encryption.$error"
|
||||
@input="v$.smtpConfig.mail_encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.from_mail.$error &&
|
||||
v$.smtpConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.from_mail"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
:invalid="v$.smtpConfig.from_mail.$error"
|
||||
@input="v$.smtpConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.from_name.$error &&
|
||||
v$.smtpConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailDriverStore.smtpConfig.from_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_name"
|
||||
:invalid="v$.smtpConfig.from_name.$error"
|
||||
@input="v$.smtpConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
|
||||
<div class="flex my-10">
|
||||
<BaseButton
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:loading="isSaving"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onMounted, ref, computed } from 'vue'
|
||||
import { required, email, numeric, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
const encryptions = reactive(['tls', 'ssl', 'starttls'])
|
||||
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
smtpConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric: helpers.withMessage(t('validation.numbers_only'), numeric),
|
||||
},
|
||||
mail_encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in mailDriverStore.smtpConfig) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
mailDriverStore.smtpConfig[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.smtpConfig.$touch()
|
||||
if (!v$.value.smtpConfig.$invalid) {
|
||||
emit('submit-data', mailDriverStore.smtpConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.smtpConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.smtpConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
136
resources/scripts/admin/views/settings/mail-sender/Index.vue
Normal file
136
resources/scripts/admin/views/settings/mail-sender/Index.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<BaseSettingCard
|
||||
:title="$tc(`${pre_t}.title`, 2)"
|
||||
:description="$t(`${pre_t}.description`)"
|
||||
>
|
||||
<MailSenderModal />
|
||||
<MailSenderTestModal />
|
||||
|
||||
<template #action>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
variant="primary-outline"
|
||||
@click="openMailSenderModal"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon :class="slotProps.class" name="PlusIcon" />
|
||||
</template>
|
||||
{{ $t(`${pre_t}.add_new_mail_sender`) }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<BaseTable
|
||||
ref="table"
|
||||
class="mt-16"
|
||||
:data="fetchData"
|
||||
:columns="mailSenderColumns"
|
||||
>
|
||||
<template #cell-is_default="{ row }">
|
||||
<BaseBadge
|
||||
:bg-color="
|
||||
utils.getBadgeStatusColor(row.data.is_default ? 'YES' : 'NO')
|
||||
.bgColor
|
||||
"
|
||||
:color="
|
||||
utils.getBadgeStatusColor(row.data.is_default ? 'YES' : 'NO').color
|
||||
"
|
||||
>
|
||||
{{ row.data.is_default ? $t('general.yes') : $t('general.no') }}
|
||||
</BaseBadge>
|
||||
</template>
|
||||
|
||||
<template #cell-actions="{ row }">
|
||||
<MailSenderDropdown
|
||||
:row="row.data"
|
||||
:table="table"
|
||||
:load-data="refreshTable"
|
||||
/>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</BaseSettingCard>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import MailSenderModal from '@/scripts/admin/components/modal-components/MailSenderModal.vue'
|
||||
import { useMailSenderStore } from '@/scripts/admin/stores/mail-sender'
|
||||
import MailSenderDropdown from '@/scripts/admin/components/dropdowns/MailSenderIndexDropdown.vue'
|
||||
import MailSenderTestModal from '@/scripts/admin/components/modal-components/MailSenderTestModal.vue'
|
||||
|
||||
const pre_t = 'settings.mail_sender'
|
||||
const modalStore = useModalStore()
|
||||
const mailSenderStore = useMailSenderStore()
|
||||
const { t } = useI18n()
|
||||
const table = ref(null)
|
||||
const utils = inject('utils')
|
||||
|
||||
function openMailSenderModal() {
|
||||
modalStore.openModal({
|
||||
title: t(`${pre_t}.add_new_mail_sender`),
|
||||
componentName: 'MailSenderModal',
|
||||
size: 'md',
|
||||
refreshData: refreshTable,
|
||||
})
|
||||
}
|
||||
|
||||
const mailSenderColumns = computed(() => {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
label: t(`${pre_t}.name`),
|
||||
thClass: 'extra',
|
||||
tdClass: 'font-medium text-gray-900',
|
||||
},
|
||||
{
|
||||
key: 'driver',
|
||||
label: t(`${pre_t}.driver`),
|
||||
thClass: 'extra',
|
||||
tdClass: 'font-medium text-gray-900',
|
||||
},
|
||||
{
|
||||
key: 'from_address',
|
||||
label: t(`${pre_t}.from_address`),
|
||||
thClass: 'extra',
|
||||
tdClass: 'font-medium text-gray-900',
|
||||
},
|
||||
{
|
||||
key: 'is_default',
|
||||
label: t(`${pre_t}.is_default`),
|
||||
thClass: 'extra',
|
||||
tdClass: 'font-medium text-gray-900',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: '',
|
||||
tdClass: 'text-right text-sm font-medium',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
async function fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await mailSenderStore.fetchMailSenders(data)
|
||||
|
||||
return {
|
||||
data: response.data.data,
|
||||
pagination: {
|
||||
totalPages: response.data.meta.last_page,
|
||||
currentPage: page,
|
||||
totalCount: response.data.meta.total,
|
||||
limit: response.data.meta.per_page ? response.data.meta.per_page : 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshTable() {
|
||||
table.value && table.value.refresh()
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<!-- Domain -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.domain`)"
|
||||
:error="v$.domain.$error && v$.domain.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.mailgunConfig.domain"
|
||||
:invalid="v$.domain.$error"
|
||||
type="text"
|
||||
@input="v$.domain.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Mailgun Secret -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.secret`)"
|
||||
:error="v$.secret.$error && v$.secret.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.mailgunConfig.secret"
|
||||
:type="getInputType"
|
||||
autocomplete="off"
|
||||
:invalid="v$.secret.$error"
|
||||
@input="v$.secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Mailgun Endpoint -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.endpoint`)"
|
||||
:error="v$.endpoint.$error && v$.endpoint.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.mailgunConfig.endpoint"
|
||||
type="text"
|
||||
:invalid="v$.endpoint.$error"
|
||||
@input="v$.endpoint.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "vue-i18n"
|
||||
import { required, email, numeric, helpers } from "@vuelidate/validators"
|
||||
import { useVuelidate } from "@vuelidate/core"
|
||||
|
||||
const pre_t = "settings.mail_sender.mailgun_config"
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
mailSenderStore: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
})
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return "text"
|
||||
}
|
||||
return "password"
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
domain: {
|
||||
required: helpers.withMessage(t("validation.required"), required),
|
||||
},
|
||||
endpoint: {
|
||||
required: helpers.withMessage(t("validation.required"), required),
|
||||
},
|
||||
secret: {
|
||||
required: helpers.withMessage(t("validation.required"), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => props.mailSenderStore.mailgunConfig)
|
||||
)
|
||||
</script>
|
||||
143
resources/scripts/admin/views/settings/mail-sender/SesDriver.vue
Normal file
143
resources/scripts/admin/views/settings/mail-sender/SesDriver.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<!-- Host -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.host`)"
|
||||
:error="v$.host.$error && v$.host.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.sesConfig.host"
|
||||
:invalid="v$.host.$error"
|
||||
type="text"
|
||||
@input="v$.host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Port -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.port`)"
|
||||
:error="v$.port.$error && v$.port.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.sesConfig.port"
|
||||
type="text"
|
||||
:invalid="v$.port.$error"
|
||||
@input="v$.port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Encryption -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.encryption`)"
|
||||
:error="v$.encryption.$error && v$.encryption.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="mailSenderStore.sesConfig.encryption"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:placeholder="$t('general.select_option')"
|
||||
:invalid="v$.encryption.$error"
|
||||
@input="v$.encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- SES Key -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.ses_key`)"
|
||||
:error="v$.ses_key.$error && v$.ses_key.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.sesConfig.ses_key"
|
||||
type="text"
|
||||
:invalid="v$.ses_key.$error"
|
||||
@input="v$.ses_key.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- SES Secret -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.ses_secret`)"
|
||||
:error="v$.ses_secret.$error && v$.ses_secret.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.sesConfig.ses_secret"
|
||||
:type="getInputType"
|
||||
autocomplete="off"
|
||||
:invalid="v$.ses_secret.$error"
|
||||
@input="v$.ses_secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { required, email, numeric, helpers } from '@vuelidate/validators'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
|
||||
const pre_t = 'settings.mail_sender.ses_config'
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
mailSenderStore: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
})
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
const encryptions = props.mailSenderStore.mail_encryptions
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric,
|
||||
},
|
||||
encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
ses_key: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
ses_secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => props.mailSenderStore.sesConfig)
|
||||
)
|
||||
</script>
|
||||
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<!-- Host -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.host`)"
|
||||
:error="v$.host.$error && v$.host.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.smtpConfig.host"
|
||||
:invalid="v$.host.$error"
|
||||
type="text"
|
||||
@input="v$.host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Port -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.port`)"
|
||||
:error="v$.port.$error && v$.port.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailSenderStore.smtpConfig.port"
|
||||
type="text"
|
||||
:invalid="v$.port.$error"
|
||||
@input="v$.port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Username -->
|
||||
<BaseInputGroup :label="$t(`${pre_t}.username`)">
|
||||
<BaseInput v-model.trim="mailSenderStore.smtpConfig.username" type="text" />
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Password -->
|
||||
<BaseInputGroup :label="$t(`${pre_t}.password`)">
|
||||
<BaseInput
|
||||
v-model="mailSenderStore.smtpConfig.password"
|
||||
:type="getInputType"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeOffIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="mr-1 text-gray-500 cursor-pointer"
|
||||
name="EyeIcon"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<!-- Encryption -->
|
||||
<BaseInputGroup
|
||||
:label="$t(`${pre_t}.encryption`)"
|
||||
:error="v$.encryption.$error && v$.encryption.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="mailSenderStore.smtpConfig.encryption"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:placeholder="$t('general.select_option')"
|
||||
:invalid="v$.encryption.$error"
|
||||
@input="v$.encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { required, numeric, helpers } from '@vuelidate/validators'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
|
||||
const pre_t = 'settings.mail_sender.smtp_config'
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
mailSenderStore: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
})
|
||||
let isShowPassword = ref(false)
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
const encryptions = props.mailSenderStore.mail_encryptions
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric: helpers.withMessage(t('validation.numbers_only'), numeric),
|
||||
},
|
||||
encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => props.mailSenderStore.smtpConfig)
|
||||
)
|
||||
</script>
|
||||
@ -162,7 +162,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { ref, computed, reactive, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import {
|
||||
|
||||
@ -54,7 +54,6 @@
|
||||
bg-white
|
||||
rounded-lg
|
||||
text-left
|
||||
overflow-hidden
|
||||
relative
|
||||
shadow-xl
|
||||
transition-all
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"customers": "العملاء",
|
||||
"items": "بضائع/خدمات",
|
||||
"invoices": "الفواتير",
|
||||
"recurring-invoices": "الفواتير المتكررة",
|
||||
"recurring-invoices": "Recurring Invoices",
|
||||
"expenses": "النفقات",
|
||||
"estimates": "التقديرات",
|
||||
"payments": "الدفوعات",
|
||||
@ -12,7 +12,7 @@
|
||||
"settings": "الإعدادات",
|
||||
"logout": "تسجيل الخروج",
|
||||
"users": "المستخدمون",
|
||||
"modules": "الإضافات"
|
||||
"modules": "Modules"
|
||||
},
|
||||
"general": {
|
||||
"add_company": "أضف شركة",
|
||||
@ -29,9 +29,9 @@
|
||||
"to_date": "إلى تاريخ",
|
||||
"from": "من",
|
||||
"to": "إلى",
|
||||
"ok": "حسناً",
|
||||
"yes": "نعم",
|
||||
"no": "لا",
|
||||
"ok": "Ok",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"sort_by": "ترتيب حسب",
|
||||
"ascending": "تصاعدي",
|
||||
"descending": "تنازلي",
|
||||
@ -39,7 +39,7 @@
|
||||
"body": "الجسم",
|
||||
"message": "رسالة",
|
||||
"send": "إرسال",
|
||||
"preview": "معاينة",
|
||||
"preview": "Preview",
|
||||
"go_back": "إلى الخلف",
|
||||
"back_to_login": "العودة إلى تسجيل الدخول؟",
|
||||
"home": "الرئيسية",
|
||||
@ -65,7 +65,7 @@
|
||||
"sent": "ارسلت",
|
||||
"all": "الكل",
|
||||
"select_all": "تحديد الكل",
|
||||
"select_template": "تحديد القالب",
|
||||
"select_template": "Select Template",
|
||||
"choose_file": "اضغط هنا لاختيار ملف",
|
||||
"choose_template": "اختيار القالب",
|
||||
"choose": "اختر",
|
||||
@ -93,14 +93,14 @@
|
||||
"no_note_found": "لم يتم العثور على الملاحظة",
|
||||
"insert_note": "أدخل ملاحظة",
|
||||
"copied_pdf_url_clipboard": "تم نسخ رابط PDF إلى الحافظة!",
|
||||
"copied_url_clipboard": "تم نسخ الرابط إلى الحافظة!",
|
||||
"docs": "المستندات",
|
||||
"do_you_wish_to_continue": "هل ترغب في المتابعة؟",
|
||||
"note": "ملاحظة",
|
||||
"pay_invoice": "سدد الفاتورة",
|
||||
"login_successfully": "تم تسجيل الدخول بنجاح!",
|
||||
"logged_out_successfully": "تم تسجيل الخروج",
|
||||
"mark_as_default": "تحديد كافتراضي"
|
||||
"copied_url_clipboard": "Copied url to clipboard!",
|
||||
"docs": "Docs",
|
||||
"do_you_wish_to_continue": "Do you wish to continue?",
|
||||
"note": "Note",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"login_successfully": "Logged in successfully!",
|
||||
"logged_out_successfully": "Logged out successfully",
|
||||
"mark_as_default": "Mark as default"
|
||||
},
|
||||
"dashboard": {
|
||||
"select_year": "اختر السنة",
|
||||
@ -109,7 +109,7 @@
|
||||
"customers": "العملاء",
|
||||
"invoices": "الفواتير",
|
||||
"estimates": "التقديرات",
|
||||
"payments": "المدفوعات"
|
||||
"payments": "Payments"
|
||||
},
|
||||
"chart_info": {
|
||||
"total_sales": "المبيعات",
|
||||
@ -151,27 +151,27 @@
|
||||
"no_results_found": "لم يتم العثور على نتائج"
|
||||
},
|
||||
"company_switcher": {
|
||||
"label": "تبديل الشركة",
|
||||
"no_results_found": "لا توجد نتائج",
|
||||
"add_new_company": "إضافة شركة جديدة",
|
||||
"new_company": "شركة جديدة",
|
||||
"created_message": "تم إنشاء الشركة بنجاح"
|
||||
"label": "SWITCH COMPANY",
|
||||
"no_results_found": "No Results Found",
|
||||
"add_new_company": "Add new company",
|
||||
"new_company": "New company",
|
||||
"created_message": "Company created successfully"
|
||||
},
|
||||
"dateRange": {
|
||||
"today": "اليوم",
|
||||
"this_week": "هذا الاسبوع",
|
||||
"this_month": "هذا الشهر",
|
||||
"this_quarter": "هذا الربع",
|
||||
"this_year": "هذه السنة",
|
||||
"previous_week": "الأسبوع السابق",
|
||||
"previous_month": "الشهر الماضي",
|
||||
"previous_quarter": "الربع السابق",
|
||||
"previous_year": "السنة الماضية",
|
||||
"custom": "مخصص"
|
||||
"today": "Today",
|
||||
"this_week": "This Week",
|
||||
"this_month": "This Month",
|
||||
"this_quarter": "This Quarter",
|
||||
"this_year": "This Year",
|
||||
"previous_week": "Previous Week",
|
||||
"previous_month": "Previous Month",
|
||||
"previous_quarter": "Previous Quarter",
|
||||
"previous_year": "Previous Year",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"customers": {
|
||||
"title": "العملاء",
|
||||
"prefix": "البادئة",
|
||||
"prefix": "Prefix",
|
||||
"add_customer": "إضافة عميل",
|
||||
"contacts_list": "قائمة العملاء",
|
||||
"name": "الاسم",
|
||||
@ -186,9 +186,9 @@
|
||||
"phone": "الهاتف",
|
||||
"website": "موقع الإنترنت",
|
||||
"overview": "استعراض",
|
||||
"invoice_prefix": "بادئة الفاتورة",
|
||||
"estimate_prefix": "بادئة رقم التقدير",
|
||||
"payment_prefix": "بادئة رقم الدفعة",
|
||||
"invoice_prefix": "Invoice Prefix",
|
||||
"estimate_prefix": "Estimate Prefix",
|
||||
"payment_prefix": "Payment Prefix",
|
||||
"enable_portal": "تفعيل البوابة",
|
||||
"country": "الدولة",
|
||||
"state": "الولاية/المنطقة",
|
||||
@ -197,7 +197,7 @@
|
||||
"added_on": "أضيف في",
|
||||
"action": "إجراء",
|
||||
"password": "كلمة المرور",
|
||||
"confirm_password": "تأكيد كلمة المرور",
|
||||
"confirm_password": "Confirm Password",
|
||||
"street_number": "رقم الشارع",
|
||||
"primary_currency": "العملة الرئيسية",
|
||||
"description": "الوصف",
|
||||
@ -208,10 +208,10 @@
|
||||
"new_customer": "عميل جديد",
|
||||
"edit_customer": "تعديل عميل",
|
||||
"basic_info": "معلوات أساسية",
|
||||
"portal_access": "الوصول إلى بوابة العملاء",
|
||||
"portal_access_text": "هل تريد السماح لهذا العميل بتسجيل الدخول إلى بوابة العملاء؟",
|
||||
"portal_access_url": "رابط بوابة العملاء",
|
||||
"portal_access_url_help": "يرجى نسخ وإعادة إرسال عنوان URL أعلاه إلى الزبون لتوفير الوصول.",
|
||||
"portal_access": "Portal Access",
|
||||
"portal_access_text": "Would you like to allow this customer to login to the Customer Portal?",
|
||||
"portal_access_url": "Customer Portal Login URL",
|
||||
"portal_access_url_help": "Please copy & forward the above given URL to your customer for providing access.",
|
||||
"billing_address": "عنوان الفوترة",
|
||||
"shipping_address": "عنوان الشحن",
|
||||
"copy_billing_address": "نسخ من عنوان الفوترة",
|
||||
@ -231,9 +231,9 @@
|
||||
"confirm_delete": "لن تتمكن من استرداد هذا العميل وجميع الفواتير والتقديرات والمدفوعات ذات الصلة. | لن تتمكن من استرداد هؤلاء العملاء وجميع الفواتير والتقديرات والمدفوعات ذات الصلة.",
|
||||
"created_message": "تم إنشاء العملاء بنجاح",
|
||||
"updated_message": "تم تحديث العملاء بنجاح",
|
||||
"address_updated_message": "تم تحديث العنوان بنجاح",
|
||||
"address_updated_message": "Address Information Updated succesfully",
|
||||
"deleted_message": "تم حذف العملاء بنجاح | تم حذف العميل بنجاح",
|
||||
"edit_currency_not_allowed": "لا يمكن تغيير العملة بمجرد إنشاء معاملة."
|
||||
"edit_currency_not_allowed": "Cannot change currency once transactions created."
|
||||
},
|
||||
"items": {
|
||||
"title": "الأصناف",
|
||||
@ -265,8 +265,8 @@
|
||||
},
|
||||
"estimates": {
|
||||
"title": "التقديرات",
|
||||
"accept_estimate": "قبول التقدير",
|
||||
"reject_estimate": "رفض التقدير",
|
||||
"accept_estimate": "Accept Estimate",
|
||||
"reject_estimate": "Reject Estimate",
|
||||
"estimate": "تقدير | تقديرات",
|
||||
"estimates_list": "قائمة التقديرات",
|
||||
"days": "{days} أيام",
|
||||
@ -318,10 +318,10 @@
|
||||
},
|
||||
"accepted": "مقبول",
|
||||
"rejected": "مرفوض",
|
||||
"expired": "انتهت مدة الصلاحية",
|
||||
"expired": "Expired",
|
||||
"sent": "مرسل",
|
||||
"draft": "مسودة",
|
||||
"viewed": "تمت المشاهدة",
|
||||
"viewed": "Viewed",
|
||||
"declined": "مرفوض",
|
||||
"new_estimate": "تقدير جديد",
|
||||
"add_new_estimate": "إضافة تقدير جديد",
|
||||
@ -355,14 +355,14 @@
|
||||
"select_an_item": "اكتب أو اختر الصنف",
|
||||
"type_item_description": "اكتب وصف الصنف (اختياري)"
|
||||
},
|
||||
"mark_as_default_estimate_template_description": "في حالة التعفيل، سيتم اختيار القالب المحدد تلقائياً للتقديرات الجديدة."
|
||||
"mark_as_default_estimate_template_description": "If enabled, the selected template will be automatically selected for new estimates."
|
||||
},
|
||||
"invoices": {
|
||||
"title": "الفواتير",
|
||||
"download": "تحميل",
|
||||
"pay_invoice": "سدد الفاتورة",
|
||||
"download": "Download",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"invoices_list": "قائمة الفواتير",
|
||||
"invoice_information": "معلومات الفاتورة",
|
||||
"invoice_information": "Invoice Information",
|
||||
"days": "{days} أيام",
|
||||
"months": "{months} أشهر",
|
||||
"years": "{years} سنوات",
|
||||
@ -397,13 +397,13 @@
|
||||
"send_invoice": "إرسال الفاتورة",
|
||||
"resend_invoice": "إعادة إرسال الفاتورة",
|
||||
"invoice_template": "قالب الفاتورة",
|
||||
"conversion_message": "تم استنساخ الفاتورة بنجاح",
|
||||
"conversion_message": "Invoice cloned successful",
|
||||
"template": "قالب",
|
||||
"mark_as_sent": "تحديد كمرسل",
|
||||
"confirm_send_invoice": "سيتم إرسال هذه الفاتورة بالبريد الألكتروني إلى العميل",
|
||||
"invoice_mark_as_sent": "سيتم تحديد هذه الفاتورة كمرسلة",
|
||||
"confirm_mark_as_accepted": "سيتم تحديد هذه الفاتورة كمقبولة",
|
||||
"confirm_mark_as_rejected": "سيتم تحديد هذه الفاتورة كمرفوضة",
|
||||
"confirm_mark_as_accepted": "This invoice will be marked as Accepted",
|
||||
"confirm_mark_as_rejected": "This invoice will be marked as Rejected",
|
||||
"confirm_send": "سيتم إرسال هذه الفاتورة بالبريد الألكتروني إلى العميل",
|
||||
"invoice_date": "تاريخ الفاتورة",
|
||||
"record_payment": "تسجيل مدفوعات",
|
||||
@ -415,13 +415,13 @@
|
||||
"update_invoice": "تحديث الفاتورة",
|
||||
"add_new_tax": "إضافة ضريبة جديدة",
|
||||
"no_invoices": "لا يوجد فواتير حتى الآن!",
|
||||
"mark_as_rejected": "تحديد كمرفوض",
|
||||
"mark_as_accepted": "تحديد كمقبول",
|
||||
"mark_as_rejected": "Mark as rejected",
|
||||
"mark_as_accepted": "Mark as accepted",
|
||||
"list_of_invoices": "قائمة الفواتير .",
|
||||
"select_invoice": "اختر الفاتورة",
|
||||
"no_matching_invoices": "لا يوجد فواتير مطابقة!",
|
||||
"mark_as_sent_successfully": "تم تحديد الفاتورة كمرسلة بنجاح",
|
||||
"invoice_sent_successfully": "تم إرسال الفاتورة بنجاح",
|
||||
"invoice_sent_successfully": "Invoice sent successfully",
|
||||
"cloned_successfully": "تم استنساخ الفاتورة بنجاح",
|
||||
"clone_invoice": "استنساخ الفاتورة",
|
||||
"confirm_clone": "سيتم استنساخ هذه الفاتورة في فاتورة جديدة",
|
||||
@ -447,14 +447,14 @@
|
||||
"marked_as_sent_message": "تم إرسال الفاتورة بنجاح",
|
||||
"something_went_wrong": "خطأ غير معروف!",
|
||||
"invalid_due_amount_message": "المبلغ النهائي للفاتورة لا يمكن أن يكون أقل من المبلغ المطلوب لها. رجاءاً حدث الفاتورة أو قم بحذف المدفوعات المرتبطة بها للاستمرار.",
|
||||
"mark_as_default_invoice_template_description": "في حالة التفعيل، سيتم اختيار القالب المحدد تلقائياً للفواتير الجديدة."
|
||||
"mark_as_default_invoice_template_description": "If enabled, the selected template will be automatically selected for new invoices."
|
||||
},
|
||||
"recurring_invoices": {
|
||||
"title": "الفواتير المتكررة",
|
||||
"invoices_list": "الفواتير المتكررة",
|
||||
"days": "{days} أيام",
|
||||
"months": "{months} أشهر",
|
||||
"years": "{years} سنوات",
|
||||
"title": "Recurring Invoices",
|
||||
"invoices_list": "Recurring Invoices List",
|
||||
"days": "{days} Days",
|
||||
"months": "{months} Month",
|
||||
"years": "{years} Year",
|
||||
"all": "All",
|
||||
"paid": "Paid",
|
||||
"unpaid": "Unpaid",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "تقدير",
|
||||
"pdf_estimate_number": "رقم تقدير",
|
||||
"pdf_estimate_date": "تاريخ التقدير",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "تاريخ انتهاء الصلاحية",
|
||||
"pdf_invoice_label": "الفاتورة",
|
||||
"pdf_invoice_number": "رقم الفاتورة",
|
||||
"pdf_invoice_date": "تاريخ الفاتورة",
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
"payments": "Platby"
|
||||
},
|
||||
"chart_info": {
|
||||
"total_sales": "Prodeje",
|
||||
"total_sales": "Slevy",
|
||||
"total_receipts": "Doklady",
|
||||
"total_expense": "Výdaje",
|
||||
"net_income": "Čistý příjem",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Odhad",
|
||||
"pdf_estimate_number": "Číslo odhadu",
|
||||
"pdf_estimate_date": "Datum odhadu",
|
||||
"pdf_estimate_expire_date": "Datum expirace",
|
||||
"pdf_estimate_expire_date": "Doba platnosti",
|
||||
"pdf_invoice_label": "Faktura",
|
||||
"pdf_invoice_number": "Číslo faktury",
|
||||
"pdf_invoice_date": "Datum fakturace",
|
||||
@ -1495,7 +1495,7 @@
|
||||
"pdf_quantity_label": "Množství",
|
||||
"pdf_price_label": "Cena",
|
||||
"pdf_discount_label": "Sleva",
|
||||
"pdf_amount_label": "Částka",
|
||||
"pdf_amount_label": "Množství",
|
||||
"pdf_subtotal": "Mezisoučet",
|
||||
"pdf_total": "Celkem",
|
||||
"pdf_payment_label": "Platba",
|
||||
|
||||
@ -688,7 +688,7 @@
|
||||
"other_modules": "Weitere Module",
|
||||
"view_all": "Alle Anzeigen",
|
||||
"no_reviews_found": "Für dieses Modul gibt es noch keine Bewertungen!",
|
||||
"module_not_purchased": "Modul noch nicht erworben",
|
||||
"module_not_purchased": "Module Not Purchased",
|
||||
"module_not_found": "Modul nicht gefunden",
|
||||
"version_not_supported": "This module version doesn't support the current version of Crater",
|
||||
"last_updated": "Zuletzt aktualisiert am",
|
||||
@ -1113,7 +1113,7 @@
|
||||
"default_currency_error": "Diese Währung wird bereits in einem der aktiven Anbieter verwendet",
|
||||
"exchange_help_text": "Wechselkurs eingeben um von {currency} nach {baseCurrency} zu konvertieren",
|
||||
"currency_freak": "CurrencyFreaks",
|
||||
"currency_layer": "Währungsebene",
|
||||
"currency_layer": "Currency Layer",
|
||||
"open_exchange_rate": "Open Exchange Rate",
|
||||
"currency_converter": "Währungsumrechner",
|
||||
"server": "Server",
|
||||
@ -1150,8 +1150,8 @@
|
||||
"payment_mode_added": "Zahlungsart hinzugefügt",
|
||||
"payment_mode_updated": "Zahlungsart aktualisiert",
|
||||
"payment_mode_confirm_delete": "Sie werden diese Zahlungsart nicht wiederherstellen können",
|
||||
"payments_attached": "Diese Zahlungsmethode ist bereits mit Zahlungen verknüpft. Bitte löschen Sie die angehängten Zahlungen, um mit der Löschung fortzufahren.",
|
||||
"expenses_attached": "Diese Zahlungsmethode ist bereits mit Ausgaben verknüpft. Bitte löschen Sie die angehängten Ausgaben, um mit der Löschung fortzufahren.",
|
||||
"payments_attached": "This payment method is already attached to payments. Please delete the attached payments to proceed with deletion.",
|
||||
"expenses_attached": "This payment method is already attached to expenses. Please delete the attached expenses to proceed with deletion.",
|
||||
"deleted_message": "Zahlungsart erfolgreich gelöscht"
|
||||
},
|
||||
"expense_category": {
|
||||
@ -1179,7 +1179,7 @@
|
||||
"discount_per_item": "Rabatt pro Artikel ",
|
||||
"discount_setting_description": "Aktivieren Sie diese Option, wenn Sie einzelnen Rechnungspositionen einen Rabatt hinzufügen möchten. Standardmäßig wird der Rabatt direkt zur Rechnung hinzugefügt.",
|
||||
"expire_public_links": "Öffentliche Links automatisch ablaufen lassen",
|
||||
"expire_setting_description": "Geben Sie an, ob Sie alle von der Anwendung gesendeten Links zur Ansicht von Rechnungen, Kostenvoranschlägen und Zahlungen usw. nach einer bestimmten Zeit ablaufen lassen möchten.",
|
||||
"expire_setting_description": "Specify whether you would like to expire all the links sent by application to view invoices, estimates & payments, etc after a specified duration.",
|
||||
"save": "Speichern",
|
||||
"preference": "Präferenz | Präferenzen",
|
||||
"general_settings": "Standardeinstellungen für das System.",
|
||||
@ -1272,14 +1272,14 @@
|
||||
"do_spaces_secret": "Do Spaces Secret",
|
||||
"do_spaces_region": "Do Spaced Region",
|
||||
"do_spaces_bucket": "Do Spaces Bucket",
|
||||
"do_spaces_endpoint": "Do Spaces Endpunkt",
|
||||
"do_spaces_endpoint": "Do Spaces Endpoint",
|
||||
"do_spaces_root": "Do Spaced Root",
|
||||
"dropbox_type": "Dropbox Typ",
|
||||
"dropbox_token": "Dropbox Token",
|
||||
"dropbox_key": "Dropbox Schlüssel",
|
||||
"dropbox_secret": "Dropbox Secret",
|
||||
"dropbox_app": "Dropbox App",
|
||||
"dropbox_root": "Dropbox Root Verzeichnis",
|
||||
"dropbox_root": "Dropbox Root",
|
||||
"default_driver": "Standard-Treiber",
|
||||
"is_default": "Standard",
|
||||
"set_default_disk": "Als Standard festlegen",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Angebot",
|
||||
"pdf_estimate_number": "Angebotsnummer",
|
||||
"pdf_estimate_date": "Angebotsdatum",
|
||||
"pdf_estimate_expire_date": "Zahlungsziel",
|
||||
"pdf_estimate_expire_date": "Gültig bis",
|
||||
"pdf_invoice_label": "Rechnung",
|
||||
"pdf_invoice_number": "Rechnungsnummer",
|
||||
"pdf_invoice_date": "Rechnungsdatum",
|
||||
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Εκτίμηση",
|
||||
"pdf_estimate_number": "Εκτίμηση Αριθμού",
|
||||
"pdf_estimate_date": "Εκτιμώμενη ημ. επισκευής",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Ημερομηνία λήξης",
|
||||
"pdf_invoice_label": "Τιμολόγιο",
|
||||
"pdf_invoice_number": "Αριθμός τιμολογίου",
|
||||
"pdf_invoice_date": "Ημ/νία Τιμολόγησης",
|
||||
|
||||
@ -100,7 +100,9 @@
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"login_successfully": "Logged in successfully!",
|
||||
"logged_out_successfully": "Logged out successfully",
|
||||
"mark_as_default": "Mark as default"
|
||||
"mark_as_default": "Mark as default",
|
||||
"select_option": "Select option",
|
||||
"send_test_mail": "Send Test Mail"
|
||||
},
|
||||
"dashboard": {
|
||||
"select_year": "Select year",
|
||||
@ -233,7 +235,8 @@
|
||||
"updated_message": "Customer updated successfully",
|
||||
"address_updated_message": "Address Information Updated succesfully",
|
||||
"deleted_message": "Customer deleted successfully | Customers deleted successfully",
|
||||
"edit_currency_not_allowed": "Cannot change currency once transactions created."
|
||||
"edit_currency_not_allowed": "Cannot change currency once transactions created.",
|
||||
"select_sender": "Select Sender"
|
||||
},
|
||||
"items": {
|
||||
"title": "Items",
|
||||
@ -728,7 +731,8 @@
|
||||
"updated_message": "User updated successfully",
|
||||
"deleted_message": "User deleted successfully | Users deleted successfully",
|
||||
"select_company_role": "Select Role for {company}",
|
||||
"companies": "Companies"
|
||||
"companies": "Companies",
|
||||
"select_sender": "Select Sender"
|
||||
},
|
||||
"reports": {
|
||||
"title": "Report",
|
||||
@ -807,7 +811,8 @@
|
||||
"payment_modes": "Payment Modes",
|
||||
"notes": "Notes",
|
||||
"exchange_rate": "Exchange Rate",
|
||||
"address_information": "Address Information"
|
||||
"address_information": "Address Information",
|
||||
"mail_sender": "Mail Senders"
|
||||
},
|
||||
"address_information": {
|
||||
"section_description": " You can update Your Address information using form below."
|
||||
@ -1311,6 +1316,51 @@
|
||||
"state_placeholder": "Example: CA",
|
||||
"zip_placeholder": "Example: 90024",
|
||||
"invalid_address": "Please provide valid address details."
|
||||
},
|
||||
"mail_sender": {
|
||||
"title": "Mail Sender | Mail Senders",
|
||||
"description": "Configure & test your mail senders for the selected company.",
|
||||
"add_new_mail_sender": "New Mail Sender",
|
||||
"name": "Sender Name",
|
||||
"name_help": "Type a name to identify the sender for users.",
|
||||
"driver": "Mail Driver",
|
||||
"is_default": "Set as default",
|
||||
"is_default_description": "You can only set one sender as default at a given time.",
|
||||
"cc": "CC",
|
||||
"bcc": "BCC",
|
||||
"from_address": "From Mail Address",
|
||||
"from_name": "From Mail Name",
|
||||
"edit_mail_sender": "Edit Mail Sender",
|
||||
"delete_mail_sender": "Delete Mail Sender",
|
||||
"confirm_delete": "You will not be able to recover this Mail Sender",
|
||||
"created_message": "Mail Sender created successfully",
|
||||
"updated_message": "Mail Sender updated successfully",
|
||||
"deleted_message": "Mail Sender deleted successfully",
|
||||
"default_record_exists": "Default mail sender already exist",
|
||||
"email_list": "Supports a comma separated list of email addresses",
|
||||
"select_mail_sender": "Select Mail Sender",
|
||||
"manage_mail_sender": "Manage Mail Senders",
|
||||
"no_mail_sender_found": "No mail senders found!",
|
||||
"no_mail_sender_found_description": "You must configure at-least one mail sender for the selected company in order to continue.",
|
||||
"smtp_config": {
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"encryption": "Encryption"
|
||||
},
|
||||
"mailgun_config": {
|
||||
"domain": "Domain",
|
||||
"secret": "Maingun Secret",
|
||||
"endpoint": "Mailgun Endpoint"
|
||||
},
|
||||
"ses_config": {
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"encryption": "Encryption",
|
||||
"ses_key": "SES Key",
|
||||
"ses_secret": "SES Secret"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wizard": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigation": {
|
||||
"dashboard": "Panel de Control",
|
||||
"dashboard": "Tablero",
|
||||
"customers": "Clientes",
|
||||
"items": "Artículos",
|
||||
"invoices": "Facturas",
|
||||
@ -9,7 +9,7 @@
|
||||
"estimates": "Presupuestos",
|
||||
"payments": "Pagos",
|
||||
"reports": "Informes",
|
||||
"settings": "Configuración",
|
||||
"settings": "Ajustes",
|
||||
"logout": "Cerrar sesión",
|
||||
"users": "Usuarios",
|
||||
"modules": "Módulos"
|
||||
@ -47,7 +47,7 @@
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"view": "Ver",
|
||||
"add_new_item": "Agregar Nuevo Artículo",
|
||||
"add_new_item": "Agregar ítem nuevo",
|
||||
"clear_all": "Limpiar todo",
|
||||
"showing": "Mostrar",
|
||||
"of": "de",
|
||||
@ -87,7 +87,7 @@
|
||||
"select_city": "Seleccionar ciudad",
|
||||
"street_1": "Calle 1",
|
||||
"street_2": "Calle 2",
|
||||
"action_failed": "Acción Fallida",
|
||||
"action_failed": "Accion Fallida",
|
||||
"retry": "Procesar de nuevo",
|
||||
"choose_note": "Elegir nota",
|
||||
"no_note_found": "No se encontró ninguna nota",
|
||||
@ -98,7 +98,7 @@
|
||||
"do_you_wish_to_continue": "¿Deseas continuar?",
|
||||
"note": "Nota",
|
||||
"pay_invoice": "Pagar factura",
|
||||
"login_successfully": "¡Sesión inciada con éxito!",
|
||||
"login_successfully": "Logeado Satisfactoriamente!",
|
||||
"logged_out_successfully": "Logeado Satisfactoriamente",
|
||||
"mark_as_default": "Marcar como predeterminado"
|
||||
},
|
||||
@ -113,7 +113,7 @@
|
||||
},
|
||||
"chart_info": {
|
||||
"total_sales": "Ventas",
|
||||
"total_receipts": "Recibos",
|
||||
"total_receipts": "Ingresos",
|
||||
"total_expense": "Gastos",
|
||||
"net_income": "Ingresos netos",
|
||||
"year": "Seleccione año"
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Presupuestar",
|
||||
"pdf_estimate_number": "Número de Presupuesto",
|
||||
"pdf_estimate_date": "Fecha presupuesto",
|
||||
"pdf_estimate_expire_date": "Fecha de vencimiento",
|
||||
"pdf_estimate_expire_date": "Fecha de caducidad",
|
||||
"pdf_invoice_label": "Factura",
|
||||
"pdf_invoice_number": "Numero de factura",
|
||||
"pdf_invoice_date": "Fecha de la factura",
|
||||
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Estimate",
|
||||
"pdf_estimate_number": "Estimate Number",
|
||||
"pdf_estimate_date": "Estimate Date",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Expiry date",
|
||||
"pdf_invoice_label": "Invoice",
|
||||
"pdf_invoice_number": "Invoice Number",
|
||||
"pdf_invoice_date": "Invoice Date",
|
||||
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Tarjous",
|
||||
"pdf_estimate_number": "Tarjousnumero",
|
||||
"pdf_estimate_date": "Tarjouksen päiväys",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Voimassaolo päivä",
|
||||
"pdf_invoice_label": "Lasku",
|
||||
"pdf_invoice_number": "Laskunumero",
|
||||
"pdf_invoice_date": "Laskun päiväys",
|
||||
|
||||
@ -310,7 +310,7 @@
|
||||
"confirm_mark_as_sent": "Ce devis sera marqué comme envoyé",
|
||||
"confirm_mark_as_accepted": "Ce devis sera marqué comme accepté",
|
||||
"confirm_mark_as_rejected": "Ce devis sera marqué comme rejeté",
|
||||
"no_matching_estimates": "Aucun devis correspondant !",
|
||||
"no_matching_estimates": "Aucune estimation correspondante !",
|
||||
"mark_as_sent_successfully": "Devis marqué comme envoyé",
|
||||
"send_estimate_successfully": "Devis envoyé",
|
||||
"errors": {
|
||||
@ -355,7 +355,7 @@
|
||||
"select_an_item": "Sélectionnez un article",
|
||||
"type_item_description": "Taper la description de l'article (facultatif)"
|
||||
},
|
||||
"mark_as_default_estimate_template_description": "Si activé, le modèle sélectionné sera automatiquement utilisé pour les nouvelles estimations."
|
||||
"mark_as_default_estimate_template_description": "If enabled, the selected template will be automatically selected for new estimates."
|
||||
},
|
||||
"invoices": {
|
||||
"title": "Factures",
|
||||
@ -447,7 +447,7 @@
|
||||
"marked_as_sent_message": "Facture supprimée | Factures supprimées",
|
||||
"something_went_wrong": "quelque chose a mal tourné",
|
||||
"invalid_due_amount_message": "Le paiement entré est supérieur au montant total dû pour cette facture. Veuillez vérifier et réessayer.",
|
||||
"mark_as_default_invoice_template_description": "Si activé, le modèle sélectionné sera automatiquement utilisé pour les nouvelles factures."
|
||||
"mark_as_default_invoice_template_description": "If enabled, the selected template will be automatically selected for new invoices."
|
||||
},
|
||||
"recurring_invoices": {
|
||||
"title": "Factures récurrentes",
|
||||
@ -526,7 +526,7 @@
|
||||
"cloned_successfully": "Facture récurrente clonée",
|
||||
"clone_invoice": "Dupliquer",
|
||||
"confirm_clone": "Cette facture récurrente sera clonée dans une nouvelle facture récurrente",
|
||||
"add_customer_email": "Veuillez ajouter une adresse e-mail pour ce client afin d'envoyer les factures automatiquement.",
|
||||
"add_customer_email": "Please add an email address for this customer to send invoices automatically.",
|
||||
"item": {
|
||||
"title": "Nom",
|
||||
"description": "Description",
|
||||
@ -844,9 +844,9 @@
|
||||
"secret": "Secret",
|
||||
"mailgun_secret": "Secret Mailgun",
|
||||
"mailgun_domain": "Domaine",
|
||||
"mailgun_endpoint": "Endpoint de Mailgun",
|
||||
"ses_secret": "Clé secrète SES",
|
||||
"ses_key": "Clé SES",
|
||||
"mailgun_endpoint": "Mailgun Endpoint",
|
||||
"ses_secret": "SES Secret",
|
||||
"ses_key": "SES Key",
|
||||
"password": "Mot de passe",
|
||||
"username": "Nom d'utilisateur",
|
||||
"mail_config": "Envoi d'emails",
|
||||
@ -1112,10 +1112,10 @@
|
||||
"error": "Vous ne pouvez pas supprimer le fournisseur actif",
|
||||
"default_currency_error": "Cette devise est déjà affectée à un fournisseur",
|
||||
"exchange_help_text": "Veuillez entrer le taux de change pour convertir {currency} en {baseCurrency}",
|
||||
"currency_freak": "Currency Freaks",
|
||||
"currency_freak": "Currency Freak",
|
||||
"currency_layer": "Currency Layer",
|
||||
"open_exchange_rate": "Ouvrir le taux de change",
|
||||
"currency_converter": "Convertisseur de devises",
|
||||
"open_exchange_rate": "Open Exchange Rate",
|
||||
"currency_converter": "Currency Converter",
|
||||
"server": "Serveur",
|
||||
"url": "URL",
|
||||
"active": "Actif",
|
||||
@ -1150,8 +1150,8 @@
|
||||
"payment_mode_added": "Mode de paiement ajouté",
|
||||
"payment_mode_updated": "Mode de paiement mis à jour",
|
||||
"payment_mode_confirm_delete": "Vous ne pourrez pas récupérer ce mode de paiement",
|
||||
"payments_attached": "Ce moyen de paiement est déjà utilisé par des paiements. Merci de supprimer les paiements associés avant de procéder à la suppression.",
|
||||
"expenses_attached": "Ce moyen de paiement est déjà utilisé par des dépenses. Merci de supprimer les dépenses associés avant de procéder à la suppression.",
|
||||
"payments_attached": "This payment method is already attached to payments. Please delete the attached payments to proceed with deletion.",
|
||||
"expenses_attached": "This payment method is already attached to expenses. Please delete the attached expenses to proceed with deletion.",
|
||||
"deleted_message": "Mode de paiement supprimé"
|
||||
},
|
||||
"expense_category": {
|
||||
@ -1262,8 +1262,8 @@
|
||||
"media_driver": "Stockage multimédia",
|
||||
"media_root": "Répertoire média",
|
||||
"aws_driver": "AWS",
|
||||
"aws_key": "Clé AWS",
|
||||
"aws_secret": "Clé secrète AWS",
|
||||
"aws_key": "AWS Key",
|
||||
"aws_secret": "AWS Secret",
|
||||
"aws_region": "Région AWS",
|
||||
"aws_bucket": "Bucket",
|
||||
"aws_root": "Répertoire",
|
||||
@ -1491,7 +1491,7 @@
|
||||
"pdf_invoice_date": "Date",
|
||||
"pdf_invoice_due_date": "Date d’échéance",
|
||||
"pdf_notes": "Notes de bas de page",
|
||||
"pdf_items_label": "Désignation",
|
||||
"pdf_items_label": "Articles",
|
||||
"pdf_quantity_label": "Quantité",
|
||||
"pdf_price_label": "Prix",
|
||||
"pdf_discount_label": "Remise",
|
||||
|
||||
@ -510,11 +510,11 @@
|
||||
"update_expense": "Update Expense",
|
||||
"edit_invoice": "Edit Recurring Invoice",
|
||||
"new_invoice": "New Recurring Invoice",
|
||||
"send_automatically": "अटोमेटिक",
|
||||
"send_automatically": "Send Automatically",
|
||||
"send_automatically_desc": "Enable this, if you would like to send the invoice automatically to the customer when its created.",
|
||||
"save_invoice": "Save Recurring Invoice",
|
||||
"update_invoice": "Update Recurring Invoice",
|
||||
"add_new_tax": "नवी कर",
|
||||
"add_new_tax": "Add New Tax",
|
||||
"no_invoices": "No Recurring Invoices yet!",
|
||||
"mark_as_rejected": "Mark as rejected",
|
||||
"mark_as_accepted": "Mark as accepted",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Estimate",
|
||||
"pdf_estimate_number": "Estimate Number",
|
||||
"pdf_estimate_date": "Estimate Date",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Expiry date",
|
||||
"pdf_invoice_label": "Invoice",
|
||||
"pdf_invoice_number": "Invoice Number",
|
||||
"pdf_invoice_date": "Invoice Date",
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"customers": "Klijenti",
|
||||
"items": "Stavke",
|
||||
"invoices": "Fakture",
|
||||
"recurring-invoices": "Ponavljajuće fakture\n",
|
||||
"recurring-invoices": "Recurring Invoices",
|
||||
"expenses": "Rashodi",
|
||||
"estimates": "Ponude",
|
||||
"payments": "Uplate",
|
||||
@ -12,7 +12,7 @@
|
||||
"settings": "Postavke",
|
||||
"logout": "Odjava",
|
||||
"users": "Korisnici",
|
||||
"modules": "Moduli"
|
||||
"modules": "Modules"
|
||||
},
|
||||
"general": {
|
||||
"add_company": "Dodaj tvrtku",
|
||||
@ -30,8 +30,8 @@
|
||||
"from": "Pošiljatelj",
|
||||
"to": "Primatelj",
|
||||
"ok": "Ok",
|
||||
"yes": "Da",
|
||||
"no": "Ne",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"sort_by": "Posloži Po",
|
||||
"ascending": "Rastuće",
|
||||
"descending": "Padajuće",
|
||||
@ -39,7 +39,7 @@
|
||||
"body": "Tijelo",
|
||||
"message": "Poruka",
|
||||
"send": "Pošalji",
|
||||
"preview": "Pregled",
|
||||
"preview": "Preview",
|
||||
"go_back": "Natrag",
|
||||
"back_to_login": "Natrag na prijavu?",
|
||||
"home": "Početna",
|
||||
@ -65,7 +65,7 @@
|
||||
"sent": "Poslano",
|
||||
"all": "Sve",
|
||||
"select_all": "Izaberi sve",
|
||||
"select_template": "Odaberite predložak",
|
||||
"select_template": "Select Template",
|
||||
"choose_file": "Klikni ovdje da izabereš fajl",
|
||||
"choose_template": "Izaberi predložak",
|
||||
"choose": "Izaberi",
|
||||
@ -93,13 +93,13 @@
|
||||
"no_note_found": "Ne postoje spremljene napomene",
|
||||
"insert_note": "Unesi bilješku",
|
||||
"copied_pdf_url_clipboard": "Link do PDF fajla kopiran!",
|
||||
"copied_url_clipboard": "URL je kopiran u međuspremnik!",
|
||||
"docs": "Dokumenti",
|
||||
"do_you_wish_to_continue": "Želite li nastaviti?",
|
||||
"note": "Bilješka",
|
||||
"pay_invoice": "Plati račun",
|
||||
"login_successfully": "Uspješno ste prijavljeni!\n",
|
||||
"logged_out_successfully": "Uspješno odjavljen\n",
|
||||
"copied_url_clipboard": "Copied url to clipboard!",
|
||||
"docs": "Docs",
|
||||
"do_you_wish_to_continue": "Do you wish to continue?",
|
||||
"note": "Note",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"login_successfully": "Logged in successfully!",
|
||||
"logged_out_successfully": "Logged out successfully",
|
||||
"mark_as_default": "Postavi kao zadano"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -109,7 +109,7 @@
|
||||
"customers": "Klijenti",
|
||||
"invoices": "Računi",
|
||||
"estimates": "Ponude",
|
||||
"payments": "Plaćanja"
|
||||
"payments": "Payments"
|
||||
},
|
||||
"chart_info": {
|
||||
"total_sales": "Prodaja",
|
||||
@ -151,18 +151,18 @@
|
||||
"no_results_found": "Nema rezultata"
|
||||
},
|
||||
"company_switcher": {
|
||||
"label": "IZABERI TVRTKU",
|
||||
"no_results_found": "Nema rezultata\n",
|
||||
"add_new_company": "Dodajte novu tvrtku\n",
|
||||
"new_company": "Nova tvrtka\n",
|
||||
"created_message": "Tvrtka uspješno stvorena\n"
|
||||
"label": "SWITCH COMPANY",
|
||||
"no_results_found": "No Results Found",
|
||||
"add_new_company": "Add new company",
|
||||
"new_company": "New company",
|
||||
"created_message": "Company created successfully"
|
||||
},
|
||||
"dateRange": {
|
||||
"today": "Danas\n",
|
||||
"this_week": "Danas\n",
|
||||
"this_month": "Danas\n",
|
||||
"this_quarter": "Danas\n",
|
||||
"this_year": "Danas\n",
|
||||
"today": "Today",
|
||||
"this_week": "This Week",
|
||||
"this_month": "This Month",
|
||||
"this_quarter": "This Quarter",
|
||||
"this_year": "This Year",
|
||||
"previous_week": "Previous Week",
|
||||
"previous_month": "Previous Month",
|
||||
"previous_quarter": "Previous Quarter",
|
||||
@ -456,32 +456,32 @@
|
||||
"months": "{months} Month",
|
||||
"years": "{years} Year",
|
||||
"all": "All",
|
||||
"paid": "Plaćeno",
|
||||
"unpaid": "Ne plaćeno",
|
||||
"viewed": "Pregledano",
|
||||
"overdue": "Kašnjenja",
|
||||
"active": "Aktivno",
|
||||
"completed": "Završeno",
|
||||
"customer": "KUPAC",
|
||||
"paid_status": "STATUS PLAĆANJA",
|
||||
"ref_no": "BR. REFERENCE",
|
||||
"number": "BROJ",
|
||||
"paid": "Paid",
|
||||
"unpaid": "Unpaid",
|
||||
"viewed": "Viewed",
|
||||
"overdue": "Overdue",
|
||||
"active": "Active",
|
||||
"completed": "Completed",
|
||||
"customer": "CUSTOMER",
|
||||
"paid_status": "PAID STATUS",
|
||||
"ref_no": "REF NO.",
|
||||
"number": "NUMBER",
|
||||
"amount_due": "AMOUNT DUE",
|
||||
"partially_paid": "Djelomično plaćeno\n",
|
||||
"total": "Ukupno",
|
||||
"discount": "Popust",
|
||||
"sub_total": "Popdzbroj",
|
||||
"invoice": "Ponavljajuća faktura | Ponavjaljuća faktura",
|
||||
"invoice_number": "Broj ponavljajuće fakture\n",
|
||||
"next_invoice_date": "Datum sljedećeg računa",
|
||||
"ref_number": "Broj reference",
|
||||
"contact": "Kontakt",
|
||||
"add_item": "Dodaj stavku",
|
||||
"date": "Datum",
|
||||
"limit_by": "Ograniči po",
|
||||
"limit_date": "Vremenski rok",
|
||||
"limit_count": "Broj ograničenja\n",
|
||||
"count": "Brojač",
|
||||
"partially_paid": "Partially Paid",
|
||||
"total": "Total",
|
||||
"discount": "Discount",
|
||||
"sub_total": "Sub Total",
|
||||
"invoice": "Recurring Invoice | Recurring Invoices",
|
||||
"invoice_number": "Recurring Invoice Number",
|
||||
"next_invoice_date": "Next Invoice Date",
|
||||
"ref_number": "Ref Number",
|
||||
"contact": "Contact",
|
||||
"add_item": "Add an Item",
|
||||
"date": "Date",
|
||||
"limit_by": "Limit by",
|
||||
"limit_date": "Limit Date",
|
||||
"limit_count": "Limit Count",
|
||||
"count": "Count",
|
||||
"status": "Status",
|
||||
"select_a_status": "Select a status",
|
||||
"working": "Working",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Ponuda",
|
||||
"pdf_estimate_number": "Broj Ponude",
|
||||
"pdf_estimate_date": "Datum Ponude",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Datum isteka Ponude",
|
||||
"pdf_invoice_label": "Faktura",
|
||||
"pdf_invoice_number": "Broj Fakture",
|
||||
"pdf_invoice_date": "Datum Fakture",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -54,9 +54,9 @@
|
||||
"actions": "Azioni",
|
||||
"subtotal": "SUBTOTALE",
|
||||
"discount": "SCONTO",
|
||||
"fixed": "Fisso",
|
||||
"fixed": "Fissato",
|
||||
"percentage": "Percentuale",
|
||||
"tax": "IMPOSTA",
|
||||
"tax": "TASSA",
|
||||
"total_amount": "AMMONTARE TOTALE",
|
||||
"bill_to": "Fattura a",
|
||||
"ship_to": "Invia a",
|
||||
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Estimate",
|
||||
"pdf_estimate_number": "Estimate Number",
|
||||
"pdf_estimate_date": "Estimate Date",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Expiry date",
|
||||
"pdf_invoice_label": "Invoice",
|
||||
"pdf_invoice_number": "Invoice Number",
|
||||
"pdf_invoice_date": "Invoice Date",
|
||||
|
||||
@ -17,6 +17,7 @@ import sk from './sk.json'
|
||||
import vi from './vi.json'
|
||||
import el from './el.json'
|
||||
import hr from './hr.json'
|
||||
import th from './th.json'
|
||||
|
||||
export default {
|
||||
cs,
|
||||
@ -37,5 +38,6 @@ export default {
|
||||
vi,
|
||||
pl,
|
||||
el,
|
||||
hr
|
||||
hr,
|
||||
th
|
||||
}
|
||||
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Pasiūlymas",
|
||||
"pdf_estimate_number": "Pasiūlymo numeris",
|
||||
"pdf_estimate_date": "Pasiūlymo data",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Galiojimo laikas",
|
||||
"pdf_invoice_label": "Sąskaita",
|
||||
"pdf_invoice_number": "Invoice Number",
|
||||
"pdf_invoice_date": "Invoice Date",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
||||
"customers": "Klanten",
|
||||
"items": "Artikelen",
|
||||
"invoices": "Facturen",
|
||||
"recurring-invoices": "Periodieke facturen",
|
||||
"recurring-invoices": "Periodieke factuur",
|
||||
"expenses": "Uitgaven",
|
||||
"estimates": "Offertes",
|
||||
"payments": "Betalingen",
|
||||
@ -28,16 +28,16 @@
|
||||
"from_date": "Vanaf datum",
|
||||
"to_date": "T/m datum",
|
||||
"from": "Vanaf",
|
||||
"to": "Naar",
|
||||
"ok": "Oké",
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
"to": "Naar.",
|
||||
"ok": "Oké.",
|
||||
"yes": "Ja.",
|
||||
"no": "Nee.",
|
||||
"sort_by": "Sorteer op",
|
||||
"ascending": "Oplopend",
|
||||
"descending": "Aflopend",
|
||||
"subject": "Onderwerp",
|
||||
"body": "Inhoud",
|
||||
"message": "Bericht",
|
||||
"message": "Bericht.",
|
||||
"send": "Verstuur",
|
||||
"preview": "Voorbeeld",
|
||||
"go_back": "Ga terug",
|
||||
@ -54,7 +54,7 @@
|
||||
"actions": "Acties",
|
||||
"subtotal": "SUBTOTAAL",
|
||||
"discount": "KORTING",
|
||||
"fixed": "Vast bedrag",
|
||||
"fixed": "Gemaakt",
|
||||
"percentage": "Percentage",
|
||||
"tax": "BELASTING",
|
||||
"total_amount": "TOTAALBEDRAG",
|
||||
@ -85,17 +85,17 @@
|
||||
"select_state": "Selecteer staat",
|
||||
"select_country": "Selecteer land",
|
||||
"select_city": "Selecteer stad",
|
||||
"street_1": "Straat 1",
|
||||
"street_2": "Straat 2",
|
||||
"action_failed": "Actie mislukt",
|
||||
"retry": "Opnieuw proberen",
|
||||
"street_1": "straat 1",
|
||||
"street_2": "Straat # 2",
|
||||
"action_failed": "Actie: mislukt",
|
||||
"retry": "Retr",
|
||||
"choose_note": "Kies notitie",
|
||||
"no_note_found": "Geen notitie gevonden",
|
||||
"insert_note": "Notitie invoegen",
|
||||
"copied_pdf_url_clipboard": "PDF link naar klembord gekopieerd!",
|
||||
"copied_url_clipboard": "URL naar klembord gekopieerd!",
|
||||
"docs": "Documentatie",
|
||||
"do_you_wish_to_continue": "Wil je doorgaan?",
|
||||
"docs": "Documenten",
|
||||
"do_you_wish_to_continue": "Wilt u Doorgaan?",
|
||||
"note": "Notitie",
|
||||
"pay_invoice": "Betaal factuur",
|
||||
"login_successfully": "Succesvol ingelogd!",
|
||||
@ -526,7 +526,7 @@
|
||||
"cloned_successfully": "Terugkerende factuur succesvol gekopieerd",
|
||||
"clone_invoice": "Kopieer periodieke factuur",
|
||||
"confirm_clone": "Deze periodieke factuur wordt gekopieerd naar een nieuwe periodieke factuur",
|
||||
"add_customer_email": "Voeg een e-mailadres aan deze klant toe zodat facturen automatisch verzonden kunnen worden.",
|
||||
"add_customer_email": "Voeg een e-mailadres aan deze klant toe, zodat facturen automatisch verzonden kunnen worden.",
|
||||
"item": {
|
||||
"title": "Item titel",
|
||||
"description": "Beschrijving",
|
||||
@ -1114,13 +1114,13 @@
|
||||
"exchange_help_text": "Voer de wisselkoers in om te converteren van {currency} naar {baseCurrency}",
|
||||
"currency_freak": "Valuta Freak",
|
||||
"currency_layer": "Valuta-laag",
|
||||
"open_exchange_rate": "Open wisselkoers",
|
||||
"open_exchange_rate": "Open Exchange Rate",
|
||||
"currency_converter": "Valuta omzetter",
|
||||
"server": "Server",
|
||||
"url": "URL",
|
||||
"active": "Actief",
|
||||
"currency_help_text": "Deze aanbieder wordt alleen gebruikt voor de hier boven geselecteerde valuta",
|
||||
"currency_in_used": "De volgende valuta zijn al actief bij een andere aanbieder. Verwijder deze valuta uit de selectie om deze aanbieder opnieuw te activeren."
|
||||
"currency_help_text": "This provider will only be used on above selected currencies",
|
||||
"currency_in_used": "The following currencies are already active on another provider. Please remove these currencies from selection to activate this provider again."
|
||||
},
|
||||
"tax_types": {
|
||||
"title": "Belastingtypen",
|
||||
@ -1143,16 +1143,16 @@
|
||||
},
|
||||
"payment_modes": {
|
||||
"title": "Betaalmethodes",
|
||||
"description": "Modi van transacties voor betalingen",
|
||||
"add_payment_mode": "Voeg betaalwijze toe",
|
||||
"edit_payment_mode": "Wijzig betaalwijze",
|
||||
"mode_name": "Modusnaam",
|
||||
"description": "Modes of transaction for payments",
|
||||
"add_payment_mode": "Add Payment Mode",
|
||||
"edit_payment_mode": "Edit Payment Mode",
|
||||
"mode_name": "Mode Name",
|
||||
"payment_mode_added": "Betaalwijze toegevoegd",
|
||||
"payment_mode_updated": "Betaalwijze bijgewerkt",
|
||||
"payment_mode_confirm_delete": "U kunt deze betalingsmodus niet herstellen",
|
||||
"payments_attached": "Deze betalingsmethode is al gekoppeld aan betalingen. Verwijder de gekoppelde betalingen om verder te gaan met het verwijderen.",
|
||||
"expenses_attached": "Deze betaalmethode is al gekoppeld aan uitgaven. Verwijder de gekoppelde kosten om door te gaan met het verwijderen.",
|
||||
"deleted_message": "Betaalwijze succesvol verwijderd"
|
||||
"payment_mode_updated": "Payment Mode Updated",
|
||||
"payment_mode_confirm_delete": "You will not be able to recover this Payment Mode",
|
||||
"payments_attached": "This payment method is already attached to payments. Please delete the attached payments to proceed with deletion.",
|
||||
"expenses_attached": "This payment method is already attached to expenses. Please delete the attached expenses to proceed with deletion.",
|
||||
"deleted_message": "Payment Mode deleted successfully"
|
||||
},
|
||||
"expense_category": {
|
||||
"title": "Onkostencategorieën",
|
||||
@ -1178,8 +1178,8 @@
|
||||
"discount_setting": "Kortingsinstelling",
|
||||
"discount_per_item": "Korting per item",
|
||||
"discount_setting_description": "Schakel dit in als u korting wilt toevoegen aan afzonderlijke factuuritems. Standaard wordt korting rechtstreeks aan de factuur toegevoegd.",
|
||||
"expire_public_links": "Publieke links automatisch laten vervallen",
|
||||
"expire_setting_description": "Geef aan of je alle links die eerder zijn verstuurd door de applicatie om facturen, offertes en betalingen te bekijken wilt laten verlopen na een bepaalde duur.",
|
||||
"expire_public_links": "Automatically Expire Public Links",
|
||||
"expire_setting_description": "Specify whether you would like to expire all the links sent by application to view invoices, estimates & payments, etc after a specified duration.",
|
||||
"save": "Opslaan",
|
||||
"preference": "Voorkeur | Voorkeuren",
|
||||
"general_settings": "Standaardvoorkeuren voor het systeem.",
|
||||
@ -1188,9 +1188,9 @@
|
||||
"select_time_zone": "Selecteer Tijdzone",
|
||||
"select_date_format": "Selecteer datum/tijdindeling",
|
||||
"select_financial_year": "Selecteer financieel ja",
|
||||
"recurring_invoice_status": "Status periodieke facturen",
|
||||
"create_status": "Status aanmaken",
|
||||
"active": "Actief",
|
||||
"recurring_invoice_status": "Recurring Invoice Status",
|
||||
"create_status": "Create Status",
|
||||
"active": "Active",
|
||||
"on_hold": "In wacht",
|
||||
"update_status": "Updatestatus",
|
||||
"completed": "Voltooid",
|
||||
@ -1217,10 +1217,10 @@
|
||||
"finishing_update": "Afwerking Update",
|
||||
"update_failed": "Update mislukt",
|
||||
"update_failed_text": "Sorry! Je update is mislukt op: {step} step ",
|
||||
"update_warning": "Alle applicatiebestanden en de standaard sjabloonbestanden worden overschreven wanneer u de applicatie met behulp van dit hulpprogramma bijwerkt. Maak een reservekopie van uw sjablonen en database voordat u deze bijwerkt."
|
||||
"update_warning": "All of the application files and default template files will be overwritten when you update the application using this utility. Please take a backup of your templates & database before updating."
|
||||
},
|
||||
"backup": {
|
||||
"title": "Back-up | Back-ups",
|
||||
"title": "Backup | Backups",
|
||||
"description": "De back-up is een zipfile met alle bestanden in de mappen die je opgeeft samen met een dump van je database",
|
||||
"new_backup": "Nieuwe back-up",
|
||||
"create_backup": "Backup maken",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"navigation": {
|
||||
"dashboard": "Pulpit",
|
||||
"customers": "Kontrahenci",
|
||||
"items": "Produkty",
|
||||
"items": "Pozycje",
|
||||
"invoices": "Faktury",
|
||||
"recurring-invoices": "Faktury cykliczne",
|
||||
"expenses": "Wydatki",
|
||||
@ -100,7 +100,7 @@
|
||||
"pay_invoice": "Zapłać Fakturę",
|
||||
"login_successfully": "Zalogowano pomyślnie!",
|
||||
"logged_out_successfully": "Wylogowano pomyślnie",
|
||||
"mark_as_default": "Oznacz jako domyślne"
|
||||
"mark_as_default": "Mark as default"
|
||||
},
|
||||
"dashboard": {
|
||||
"select_year": "Wybierz rok",
|
||||
@ -355,7 +355,7 @@
|
||||
"select_an_item": "Wpisz lub kliknij aby wybrać element",
|
||||
"type_item_description": "Opis pozycji (opcjonalnie)"
|
||||
},
|
||||
"mark_as_default_estimate_template_description": "Jeśli włączone, wybrany szablon zostanie automatycznie wybrany dla nowych ofert."
|
||||
"mark_as_default_estimate_template_description": "If enabled, the selected template will be automatically selected for new estimates."
|
||||
},
|
||||
"invoices": {
|
||||
"title": "Faktury",
|
||||
@ -447,7 +447,7 @@
|
||||
"marked_as_sent_message": "Faktura oznaczona jako wysłana pomyślnie",
|
||||
"something_went_wrong": "coś poszło nie tak",
|
||||
"invalid_due_amount_message": "Całkowita kwota faktury nie może być mniejsza niż całkowita kwota zapłacona za tę fakturę. Proszę zaktualizować fakturę lub usunąć powiązane płatności, aby kontynuować.",
|
||||
"mark_as_default_invoice_template_description": "Jeśli włączone, wybrany szablon zostanie automatycznie wybrany dla nowych faktur."
|
||||
"mark_as_default_invoice_template_description": "If enabled, the selected template will be automatically selected for new invoices."
|
||||
},
|
||||
"recurring_invoices": {
|
||||
"title": "Faktury cykliczne",
|
||||
@ -526,7 +526,7 @@
|
||||
"cloned_successfully": "Faktura Cykliczna sklonowana pomyślnie",
|
||||
"clone_invoice": "Klonuj Fakturę Cykliczną",
|
||||
"confirm_clone": "Ta faktura cykliczna zostanie sklonowana do nowej faktury cyklicznej",
|
||||
"add_customer_email": "Dodaj adres e-mail dla tego klienta, aby wysyłać faktury automatycznie.",
|
||||
"add_customer_email": "Please add an email address for this customer to send invoices automatically.",
|
||||
"item": {
|
||||
"title": "Tytuł pozycji",
|
||||
"description": "Opis",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Oferta",
|
||||
"pdf_estimate_number": "Numer oferty",
|
||||
"pdf_estimate_date": "Data oferty",
|
||||
"pdf_estimate_expire_date": "Data ważności",
|
||||
"pdf_estimate_expire_date": "Termin ważności",
|
||||
"pdf_invoice_label": "Faktura",
|
||||
"pdf_invoice_number": "Numer faktury",
|
||||
"pdf_invoice_date": "Data faktury",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -97,8 +97,8 @@
|
||||
"docs": "Documentație",
|
||||
"do_you_wish_to_continue": "Doriţi să continuaţi?",
|
||||
"note": "Note",
|
||||
"pay_invoice": "Plătește factura",
|
||||
"login_successfully": "Autentificat cu succes!",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"login_successfully": "Logged in successfully!",
|
||||
"logged_out_successfully": "Logged out successfully",
|
||||
"mark_as_default": "Mark as default"
|
||||
},
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Estimate",
|
||||
"pdf_estimate_number": "Estimate Number",
|
||||
"pdf_estimate_date": "Estimate Date",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Expiry date",
|
||||
"pdf_invoice_label": "Invoice",
|
||||
"pdf_invoice_number": "Invoice Number",
|
||||
"pdf_invoice_date": "Invoice Date",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
||||
"customers": "Zákazníci",
|
||||
"items": "Položky",
|
||||
"invoices": "Faktúry",
|
||||
"recurring-invoices": "Pravidelné faktúry",
|
||||
"recurring-invoices": "Recurring Invoices",
|
||||
"expenses": "Výdaje",
|
||||
"estimates": "Cenové odhady",
|
||||
"payments": "Platby",
|
||||
@ -12,7 +12,7 @@
|
||||
"settings": "Nastavenia",
|
||||
"logout": "Odhlásiť sa",
|
||||
"users": "Uživatelia",
|
||||
"modules": "Moduly"
|
||||
"modules": "Modules"
|
||||
},
|
||||
"general": {
|
||||
"add_company": "Pridať firmu",
|
||||
@ -30,8 +30,8 @@
|
||||
"from": "Od",
|
||||
"to": "Pre",
|
||||
"ok": "Ok",
|
||||
"yes": "Áno",
|
||||
"no": "Nie",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"sort_by": "Zoradiť podľa",
|
||||
"ascending": "Vzostupne",
|
||||
"descending": "Zostupne",
|
||||
@ -39,7 +39,7 @@
|
||||
"body": "Telo textu",
|
||||
"message": "Správa",
|
||||
"send": "Odoslať",
|
||||
"preview": "Náhľad",
|
||||
"preview": "Preview",
|
||||
"go_back": "Späť",
|
||||
"back_to_login": "Späť na prihlásenie?",
|
||||
"home": "Domov",
|
||||
@ -65,7 +65,7 @@
|
||||
"sent": "Odoslané",
|
||||
"all": "Všetko",
|
||||
"select_all": "Vybrať všetky",
|
||||
"select_template": "Vyberte šablónu",
|
||||
"select_template": "Select Template",
|
||||
"choose_file": "Kliknite sem pre vybratie súboru",
|
||||
"choose_template": "Vybrať vzhľad",
|
||||
"choose": "Vybrať",
|
||||
@ -92,15 +92,15 @@
|
||||
"choose_note": "Vyberte poznámku",
|
||||
"no_note_found": "Neboli nájdené žiadne poznámky",
|
||||
"insert_note": "Vlož poznámku",
|
||||
"copied_pdf_url_clipboard": "PDF bolo skopírované do schránky!",
|
||||
"copied_url_clipboard": "URL adresa bola skopírovaná do schránky!",
|
||||
"copied_pdf_url_clipboard": "Copied PDF url to clipboard!",
|
||||
"copied_url_clipboard": "Copied url to clipboard!",
|
||||
"docs": "Docs",
|
||||
"do_you_wish_to_continue": "Želáte si pokračovať?",
|
||||
"note": "Poznámka",
|
||||
"pay_invoice": "Zaplatiť faktúru",
|
||||
"login_successfully": "Prihlásenie bolo úspešné!",
|
||||
"logged_out_successfully": "Odhlásenie bolo úspešné",
|
||||
"mark_as_default": "Označiť ako predvolené"
|
||||
"do_you_wish_to_continue": "Do you wish to continue?",
|
||||
"note": "Note",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"login_successfully": "Logged in successfully!",
|
||||
"logged_out_successfully": "Logged out successfully",
|
||||
"mark_as_default": "Mark as default"
|
||||
},
|
||||
"dashboard": {
|
||||
"select_year": "Vyberte rok",
|
||||
@ -109,7 +109,7 @@
|
||||
"customers": "Zákazníci",
|
||||
"invoices": "Faktúry",
|
||||
"estimates": "Cenové odhady",
|
||||
"payments": "Platby"
|
||||
"payments": "Payments"
|
||||
},
|
||||
"chart_info": {
|
||||
"total_sales": "Predaje",
|
||||
@ -151,27 +151,27 @@
|
||||
"no_results_found": "Neboli nájdené žiadne výsledky"
|
||||
},
|
||||
"company_switcher": {
|
||||
"label": "PREPNÚŤ SPOLOČNOSŤ",
|
||||
"no_results_found": "Neboli nájdené žiadne výsledky",
|
||||
"add_new_company": "Pridať novú spoločnosť",
|
||||
"new_company": "Vytvorte spoločnosť",
|
||||
"created_message": "Spoločnosť úspešne vytvorená"
|
||||
"label": "SWITCH COMPANY",
|
||||
"no_results_found": "No Results Found",
|
||||
"add_new_company": "Add new company",
|
||||
"new_company": "New company",
|
||||
"created_message": "Company created successfully"
|
||||
},
|
||||
"dateRange": {
|
||||
"today": "Dnes",
|
||||
"this_week": "Tento týždeň",
|
||||
"this_month": "Tento mesiac",
|
||||
"this_quarter": "Tento štvrťrok",
|
||||
"this_year": "Tento rok",
|
||||
"previous_week": "Predchádzajúci týždeň",
|
||||
"previous_month": "Predchádzajúci mesiac",
|
||||
"previous_quarter": "Predchádzajúci štvrťrok",
|
||||
"previous_year": "Predchádzajúci rok",
|
||||
"custom": "Vlastný"
|
||||
"today": "Today",
|
||||
"this_week": "This Week",
|
||||
"this_month": "This Month",
|
||||
"this_quarter": "This Quarter",
|
||||
"this_year": "This Year",
|
||||
"previous_week": "Previous Week",
|
||||
"previous_month": "Previous Month",
|
||||
"previous_quarter": "Previous Quarter",
|
||||
"previous_year": "Previous Year",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"customers": {
|
||||
"title": "Zákazníci",
|
||||
"prefix": "Predpona",
|
||||
"prefix": "Prefix",
|
||||
"add_customer": "Pridať Zákazníka",
|
||||
"contacts_list": "Zoznam zákazníkov",
|
||||
"name": "Meno",
|
||||
@ -186,9 +186,9 @@
|
||||
"phone": "Telefón",
|
||||
"website": "Webové stránky",
|
||||
"overview": "Prehľad",
|
||||
"invoice_prefix": "Predpona Faktúry",
|
||||
"estimate_prefix": "Predpona cenového odhadu",
|
||||
"payment_prefix": "Predpona platby",
|
||||
"invoice_prefix": "Invoice Prefix",
|
||||
"estimate_prefix": "Estimate Prefix",
|
||||
"payment_prefix": "Payment Prefix",
|
||||
"enable_portal": "Aktivovať portál",
|
||||
"country": "Krajina",
|
||||
"state": "Štát",
|
||||
@ -197,7 +197,7 @@
|
||||
"added_on": "Pridané Dňa",
|
||||
"action": "Akcia",
|
||||
"password": "Heslo",
|
||||
"confirm_password": "Potvrdiť heslo",
|
||||
"confirm_password": "Confirm Password",
|
||||
"street_number": "Číslo Ulice",
|
||||
"primary_currency": "Hlavná Mena",
|
||||
"description": "Popis",
|
||||
@ -208,17 +208,17 @@
|
||||
"new_customer": "Nový Zákazník",
|
||||
"edit_customer": "Upraviť Zákazníka",
|
||||
"basic_info": "Základné Informácie",
|
||||
"portal_access": "Prístupy na portál",
|
||||
"portal_access_text": "Chcete povoliť tomuto používateľovi pripojiť sa na Zákaznícky Portál?",
|
||||
"portal_access_url": "Adresa zákazníckeho portálu",
|
||||
"portal_access_url_help": "Prosím, skopírujte a prepošlite URL adresu uvedenú vyššie vášmu klientovi pre udelenie prístupu.",
|
||||
"portal_access": "Portal Access",
|
||||
"portal_access_text": "Would you like to allow this customer to login to the Customer Portal?",
|
||||
"portal_access_url": "Customer Portal Login URL",
|
||||
"portal_access_url_help": "Please copy & forward the above given URL to your customer for providing access.",
|
||||
"billing_address": "Fakturačná Adresa",
|
||||
"shipping_address": "Doručovacia Adresa",
|
||||
"copy_billing_address": "Kopírovať podľa Fakturačnej adresy",
|
||||
"no_customers": "Zatiaľ nebol pridaný žiadny zákazník!",
|
||||
"no_customers_found": "Nenájdení žiadni zákazníci!",
|
||||
"no_contact": "Žiadny kontakt",
|
||||
"no_contact_name": "Žiadny kontakt",
|
||||
"no_contact": "No contact",
|
||||
"no_contact_name": "No contact name",
|
||||
"list_of_customers": "Táto sekcia bude obsahovať zoznam zákazníkov.",
|
||||
"primary_display_name": "Hlavné meno pre zobrazenie",
|
||||
"select_currency": "Vyberte menu",
|
||||
@ -231,7 +231,7 @@
|
||||
"confirm_delete": "Nebudete môcť obnoviť tohto zákazníka ani žiadne faktúry, cenové odhady alebo platby s ním spojené. | Nebudete môcť obnoviť týchto zákazníkov ani žiadne faktúry, cenové odhady alebo platby s nimi spojené.",
|
||||
"created_message": "Zákazník úspešne vytvorený",
|
||||
"updated_message": "Zákazník úspešne aktualizovaný",
|
||||
"address_updated_message": "Nastavenia adresy úspešne aktualizované",
|
||||
"address_updated_message": "Address Information Updated succesfully",
|
||||
"deleted_message": "Zákazník úspešne odstránený | Zákazníci úspešne odstránení",
|
||||
"edit_currency_not_allowed": "Cannot change currency once transactions created."
|
||||
},
|
||||
@ -244,7 +244,7 @@
|
||||
"added_on": "Pridané Dňa",
|
||||
"price": "Cena",
|
||||
"date_of_creation": "Dátum Vytvorenia",
|
||||
"not_selected": "Nie je vybratá žiadna položka",
|
||||
"not_selected": "No item selected",
|
||||
"action": "Akcia",
|
||||
"add_item": "Pridať Položku",
|
||||
"save_item": "Uložiť Položku",
|
||||
@ -317,7 +317,7 @@
|
||||
"required": "Pole je povinné"
|
||||
},
|
||||
"accepted": "Prijátá",
|
||||
"rejected": "Odmietnuté",
|
||||
"rejected": "Rejected",
|
||||
"expired": "Expired",
|
||||
"sent": "Odoslaná",
|
||||
"draft": "Koncept",
|
||||
@ -359,8 +359,8 @@
|
||||
},
|
||||
"invoices": {
|
||||
"title": "Faktúry",
|
||||
"download": "Stiahnuť",
|
||||
"pay_invoice": "Zaplatiť faktúru",
|
||||
"download": "Download",
|
||||
"pay_invoice": "Pay Invoice",
|
||||
"invoices_list": "Zoznam Faktúr",
|
||||
"invoice_information": "Invoice Information",
|
||||
"days": "{days} Ďeň",
|
||||
@ -370,8 +370,8 @@
|
||||
"paid": "Zaplatené",
|
||||
"unpaid": "Nezaplatené",
|
||||
"viewed": "Viewed",
|
||||
"overdue": "Po splatnosti",
|
||||
"completed": "Dokončené",
|
||||
"overdue": "Overdue",
|
||||
"completed": "Completed",
|
||||
"customer": "ZÁKAZNÍK",
|
||||
"paid_status": "Stav platby",
|
||||
"ref_no": "REF Č.",
|
||||
@ -408,7 +408,7 @@
|
||||
"invoice_date": "Dátum Vystavenia",
|
||||
"record_payment": "Zaznamenať Platbu",
|
||||
"add_new_invoice": "Nová Faktúra",
|
||||
"update_expense": "Aktualizovať Výdaj",
|
||||
"update_expense": "Update Expense",
|
||||
"edit_invoice": "Upraviť Faktúru",
|
||||
"new_invoice": "Nová Faktúra",
|
||||
"save_invoice": "Uložiť Faktúru",
|
||||
@ -421,7 +421,7 @@
|
||||
"select_invoice": "Vybrať Faktúru",
|
||||
"no_matching_invoices": "Nenašli sa žiadne faktúry!",
|
||||
"mark_as_sent_successfully": "Faktúra označená ako úspešne odoslaná",
|
||||
"invoice_sent_successfully": "Faktúra bola úspešne odoslaná",
|
||||
"invoice_sent_successfully": "Invoice sent successfully",
|
||||
"cloned_successfully": "Faktúra bola úspešne okopírovaná",
|
||||
"clone_invoice": "Kopírovať faktúru",
|
||||
"confirm_clone": "Faktúra bude okopírovaná do novej",
|
||||
@ -450,34 +450,34 @@
|
||||
"mark_as_default_invoice_template_description": "If enabled, the selected template will be automatically selected for new invoices."
|
||||
},
|
||||
"recurring_invoices": {
|
||||
"title": "Pravidelné faktúry",
|
||||
"invoices_list": "Zoznam pravidelných faktúr",
|
||||
"title": "Recurring Invoices",
|
||||
"invoices_list": "Recurring Invoices List",
|
||||
"days": "{days} Days",
|
||||
"months": "{months} Month",
|
||||
"years": "{years} Year",
|
||||
"all": "Všetko",
|
||||
"paid": "Zaplatené",
|
||||
"unpaid": "Nezaplatené",
|
||||
"all": "All",
|
||||
"paid": "Paid",
|
||||
"unpaid": "Unpaid",
|
||||
"viewed": "Viewed",
|
||||
"overdue": "Po splatnosti",
|
||||
"overdue": "Overdue",
|
||||
"active": "Active",
|
||||
"completed": "Dokončené",
|
||||
"customer": "ZÁKAZNÍK",
|
||||
"completed": "Completed",
|
||||
"customer": "CUSTOMER",
|
||||
"paid_status": "PAID STATUS",
|
||||
"ref_no": "REF Č.",
|
||||
"number": "ČÍSLO",
|
||||
"ref_no": "REF NO.",
|
||||
"number": "NUMBER",
|
||||
"amount_due": "AMOUNT DUE",
|
||||
"partially_paid": "Čiastočne Zaplatené",
|
||||
"total": "Celkom",
|
||||
"discount": "Zľava",
|
||||
"sub_total": "Medzisúčet",
|
||||
"invoice": "Pravidelná faktúra | Pravidelné faktúry",
|
||||
"invoice_number": "Číslo pravidelnej faktúry",
|
||||
"next_invoice_date": "Dátum nasledujúceho vystavenia",
|
||||
"ref_number": "Ref. Číslo",
|
||||
"contact": "Kontakt",
|
||||
"add_item": "Pridať položku",
|
||||
"date": "Dátum",
|
||||
"partially_paid": "Partially Paid",
|
||||
"total": "Total",
|
||||
"discount": "Discount",
|
||||
"sub_total": "Sub Total",
|
||||
"invoice": "Recurring Invoice | Recurring Invoices",
|
||||
"invoice_number": "Recurring Invoice Number",
|
||||
"next_invoice_date": "Next Invoice Date",
|
||||
"ref_number": "Ref Number",
|
||||
"contact": "Contact",
|
||||
"add_item": "Add an Item",
|
||||
"date": "Date",
|
||||
"limit_by": "Limit by",
|
||||
"limit_date": "Limit Date",
|
||||
"limit_count": "Limit Count",
|
||||
@ -506,15 +506,15 @@
|
||||
"starts_at": "Start Date",
|
||||
"due_date": "Invoice Due Date",
|
||||
"record_payment": "Record Payment",
|
||||
"add_new_invoice": "Vytvoriť novú pravidelnú faktúru",
|
||||
"update_expense": "Aktualizovať Výdaj",
|
||||
"edit_invoice": "Upraviť pravidelnú faktúru",
|
||||
"new_invoice": "Nová pravidelná faktúra",
|
||||
"send_automatically": "Odoslať automaticky",
|
||||
"add_new_invoice": "Add New Recurring Invoice",
|
||||
"update_expense": "Update Expense",
|
||||
"edit_invoice": "Edit Recurring Invoice",
|
||||
"new_invoice": "New Recurring Invoice",
|
||||
"send_automatically": "Send Automatically",
|
||||
"send_automatically_desc": "Enable this, if you would like to send the invoice automatically to the customer when its created.",
|
||||
"save_invoice": "Uložiť pravidelnú faktúru",
|
||||
"update_invoice": "Upraviť pravidelnú faktúru",
|
||||
"add_new_tax": "Pridať novú daň",
|
||||
"save_invoice": "Save Recurring Invoice",
|
||||
"update_invoice": "Update Recurring Invoice",
|
||||
"add_new_tax": "Add New Tax",
|
||||
"no_invoices": "No Recurring Invoices yet!",
|
||||
"mark_as_rejected": "Mark as rejected",
|
||||
"mark_as_accepted": "Mark as accepted",
|
||||
@ -528,27 +528,27 @@
|
||||
"confirm_clone": "This recurring invoice will be cloned into a new Recurring Invoice",
|
||||
"add_customer_email": "Please add an email address for this customer to send invoices automatically.",
|
||||
"item": {
|
||||
"title": "Názov položky",
|
||||
"description": "Popis",
|
||||
"quantity": "Množstvo",
|
||||
"price": "Cena",
|
||||
"discount": "Zľava",
|
||||
"total": "Súčet",
|
||||
"total_discount": "Celková zľava",
|
||||
"sub_total": "Medzisúčet",
|
||||
"tax": "Daň",
|
||||
"amount": "Množstvo",
|
||||
"select_an_item": "Začnite písať alebo kliknite pre vybratie položky",
|
||||
"type_item_description": "Popis položky (voliteľné)"
|
||||
"title": "Item Title",
|
||||
"description": "Description",
|
||||
"quantity": "Quantity",
|
||||
"price": "Price",
|
||||
"discount": "Discount",
|
||||
"total": "Total",
|
||||
"total_discount": "Total Discount",
|
||||
"sub_total": "Sub Total",
|
||||
"tax": "Tax",
|
||||
"amount": "Amount",
|
||||
"select_an_item": "Type or click to select an item",
|
||||
"type_item_description": "Type Item Description (optional)"
|
||||
},
|
||||
"frequency": {
|
||||
"title": "Frekvencia",
|
||||
"select_frequency": "Vybrať frekvenciu",
|
||||
"minute": "Minúta",
|
||||
"hour": "Hodina",
|
||||
"day_month": "Deň v mesiaci",
|
||||
"month": "Mesiac",
|
||||
"day_week": "Deň v týždni"
|
||||
"title": "Frequency",
|
||||
"select_frequency": "Select Frequency",
|
||||
"minute": "Minute",
|
||||
"hour": "Hour",
|
||||
"day_month": "Day of month",
|
||||
"month": "Month",
|
||||
"day_week": "Day of week"
|
||||
},
|
||||
"confirm_delete": "You will not be able to recover this Invoice | You will not be able to recover these Invoices",
|
||||
"created_message": "Recurring Invoice created successfully",
|
||||
@ -686,12 +686,12 @@
|
||||
"api_token": "API token",
|
||||
"invalid_api_token": "Invalid API Token.",
|
||||
"other_modules": "Other Modules",
|
||||
"view_all": "Zobraziť všetky",
|
||||
"no_reviews_found": "Zatiaľ nie sú dostupné žiadne hodnotenia pre tento modul!",
|
||||
"module_not_purchased": "Modul nie je kúpený",
|
||||
"module_not_found": "Modul nebol nájdený",
|
||||
"view_all": "View All",
|
||||
"no_reviews_found": "There are no reviews for this module yet!",
|
||||
"module_not_purchased": "Module Not Purchased",
|
||||
"module_not_found": "Module Not Found",
|
||||
"version_not_supported": "This module version doesn't support the current version of Crater",
|
||||
"last_updated": "Naposledy aktualizované",
|
||||
"last_updated": "Last Updated On",
|
||||
"connect_installation": "Connect your installation",
|
||||
"api_token_description": "Login to {url} and connect this installation by entering the API Token. Your purchased modules will show up here after the connection is established.",
|
||||
"view_module": "View Module",
|
||||
@ -806,8 +806,8 @@
|
||||
"custom_fields": "Vlastné Polia",
|
||||
"payment_modes": "Spôsoby Platby",
|
||||
"notes": "Poznámky",
|
||||
"exchange_rate": "Výmenný kurz",
|
||||
"address_information": "Údaje adresy"
|
||||
"exchange_rate": "Exchange Rate",
|
||||
"address_information": "Address Information"
|
||||
},
|
||||
"address_information": {
|
||||
"section_description": " You can update Your Address information using form below."
|
||||
@ -872,9 +872,9 @@
|
||||
"address": "Adresa",
|
||||
"zip": "PSČ",
|
||||
"save": "Uložiť",
|
||||
"delete": "Odstrániť",
|
||||
"delete": "Delete",
|
||||
"updated_message": "Informácie o firme úspešne aktualizované",
|
||||
"delete_company": "Odstrániť spoločnosť",
|
||||
"delete_company": "Delete Company",
|
||||
"delete_company_description": "Once you delete your company, you will lose all the data and files associated with it permanently.",
|
||||
"are_you_absolutely_sure": "Are you absolutely sure?",
|
||||
"delete_company_modal_desc": "This action cannot be undone. This will permanently delete {company} and all of its associated data.",
|
||||
@ -1485,7 +1485,7 @@
|
||||
"pdf_estimate_label": "Cenový odhad",
|
||||
"pdf_estimate_number": "Číslo cenového odhadu",
|
||||
"pdf_estimate_date": "Dátum cenového odhadu",
|
||||
"pdf_estimate_expire_date": "Expiry Date",
|
||||
"pdf_estimate_expire_date": "Platnosť cenového odhadu",
|
||||
"pdf_invoice_label": "Faktúra",
|
||||
"pdf_invoice_number": "Číslo faktúry",
|
||||
"pdf_invoice_date": "Dátum vystavenia",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user