Merge branch 'master' into master

This commit is contained in:
Claret Nnamocha
2019-12-20 09:35:03 +01:00
committed by GitHub
69 changed files with 2533 additions and 264 deletions

View File

@ -1,6 +1,6 @@
APP_ENV=production
APP_KEY=base64:kgk/4DW1vEVy7aEvet5FPp5un6PIGe/so8H0mvoUtW0=
APP_DEBUG=false
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://crater.test

26
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Please complete the following information:**
- Crater version:
- PHP version:
- Database type and version:
**Optional info**
- OS: [e.g. Ubuntu]
- Browser: [e.g. chrome, safari]

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

View File

@ -23,8 +23,8 @@ FROM php:7.3.12-fpm-alpine
# Use the default production configuration
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN apk add --no-cache libpng-dev libxml2-dev oniguruma-dev && \
docker-php-ext-install bcmath ctype json gd mbstring pdo pdo_mysql tokenizer xml
RUN apk add --no-cache libpng-dev libxml2-dev oniguruma-dev libzip-dev && \
docker-php-ext-install bcmath ctype json gd mbstring pdo pdo_mysql tokenizer xml zip
# Set container's working dir
WORKDIR /app

View File

@ -49,15 +49,20 @@ class Estimate extends Model
];
protected $casts = [
'total' => 'float',
'tax' => 'float',
'sub_total' => 'float'
'total' => 'integer',
'tax' => 'integer',
'sub_total' => 'integer',
'discount' => 'float',
'discount_val' => 'integer',
];
public static function getNextEstimateNumber()
public static function getNextEstimateNumber($value)
{
// Get the last created order
$lastOrder = Estimate::orderBy('created_at', 'desc')->first();
// Get the last created order
$lastOrder = Estimate::where('estimate_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
@ -99,10 +104,16 @@ class Estimate extends Model
public function getEstimateNumAttribute()
{
$position = $this->strposX($this->estimate_number, "-", 2) + 1;
$position = $this->strposX($this->estimate_number, "-", 1) + 1;
return substr($this->estimate_number, $position);
}
public function getEstimatePrefixAttribute()
{
$prefix = explode("-",$this->estimate_number)[0];
return $prefix;
}
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {

View File

@ -146,7 +146,8 @@ class CompanyController extends Controller
$languages = [
["code"=>"en", "name" => "English"],
["code"=>"fr", "name" => "French"],
["code"=>"es", "name" => "Spanish"]
["code"=>"es", "name" => "Spanish"],
["code"=>"ar", "name" => "العربية"],
];
return response()->json([
@ -190,6 +191,58 @@ class CompanyController extends Controller
'success' => true
]);
}
public function getCustomizeSetting (Request $request)
{
$invoice_prefix = CompanySetting::getSetting('invoice_prefix', $request->header('company'));
$invoice_auto_generate = CompanySetting::getSetting('invoice_auto_generate', $request->header('company'));
$estimate_prefix = CompanySetting::getSetting('estimate_prefix', $request->header('company'));
$estimate_auto_generate = CompanySetting::getSetting('estimate_auto_generate', $request->header('company'));
$payment_prefix = CompanySetting::getSetting('payment_prefix', $request->header('company'));
$payment_auto_generate = CompanySetting::getSetting('payment_auto_generate', $request->header('company'));
return response()->json([
'invoice_prefix' => $invoice_prefix,
'invoice_auto_generate' => $invoice_auto_generate,
'estimate_prefix' => $estimate_prefix,
'estimate_auto_generate' => $estimate_auto_generate,
'payment_prefix' => $payment_prefix,
'payment_auto_generate' => $payment_auto_generate,
]);
}
public function updateCustomizeSetting (Request $request)
{
$sets = [];
if ($request->type == "PAYMENTS") {
$sets = [
'payment_prefix'
];
}
if ($request->type == "INVOICES") {
$sets = [
'invoice_prefix',
];
}
if ($request->type == "ESTIMATES") {
$sets = [
'estimate_prefix',
];
}
foreach ($sets as $key) {
CompanySetting::setSetting($key, $request->$key, $request->header('company'));
}
return response()->json([
'success' => true
]);
}
/**
* Update a specific Company Setting

View File

@ -177,6 +177,7 @@ class CustomersController extends Controller
$customer->enable_portal = $request->enable_portal;
$customer->save();
$customer->addresses()->delete();
if ($request->addresses) {
foreach ($request->addresses as $address) {
$newAddress = $customer->addresses()->firstOrNew(['type' => $address["type"]]);

View File

@ -56,25 +56,43 @@ class EstimatesController extends Controller
public function create(Request $request)
{
$nextEstimateNumber = 'EST-'.Estimate::getNextEstimateNumber();
$estimate_prefix = CompanySetting::getSetting('estimate_prefix', $request->header('company'));
$estimate_num_auto_generate = CompanySetting::getSetting('estimate_auto_generate', $request->header('company'));
$nextEstimateNumberAttribute = null;
$nextEstimateNumber = Estimate::getNextEstimateNumber($estimate_prefix);
if ($estimate_num_auto_generate == "YES") {
$nextEstimateNumberAttribute = $nextEstimateNumber;
}
$tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company'));
$discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company'));
$customers = User::where('role', 'customer')->get();
return response()->json([
'customers' => $customers,
'nextEstimateNumber' => $nextEstimateNumber,
'nextEstimateNumberAttribute' => $nextEstimateNumberAttribute,
'nextEstimateNumber' => $estimate_prefix.'-'.$nextEstimateNumber,
'taxes' => Tax::whereCompany($request->header('company'))->latest()->get(),
'items' => Item::whereCompany($request->header('company'))->get(),
'tax_per_item' => $tax_per_item,
'discount_per_item' => $discount_per_item,
'estimateTemplates' => EstimateTemplate::all(),
'shareable_link' => ''
'shareable_link' => '',
'estimate_prefix' => $estimate_prefix
]);
}
public function store(EstimatesRequest $request)
{
$estimate_number = explode("-",$request->estimate_number);
$number_attributes['estimate_number'] = $estimate_number[0].'-'.sprintf('%06d', intval($estimate_number[1]));
Validator::make($number_attributes, [
'estimate_number' => 'required|unique:estimates,estimate_number'
])->validate();
$estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date);
$expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date);
$status = Estimate::STATUS_DRAFT;
@ -101,7 +119,7 @@ class EstimatesController extends Controller
$estimate = Estimate::create([
'estimate_date' => $estimate_date,
'expiry_date' => $expiry_date,
'estimate_number' => $request->estimate_number,
'estimate_number' => $number_attributes['estimate_number'],
'reference_number' => $request->reference_number,
'user_id' => $request->user_id,
'company_id' => $request->header('company'),
@ -127,7 +145,7 @@ class EstimatesController extends Controller
if (array_key_exists('taxes', $estimateItem) && $estimateItem['taxes']) {
foreach ($estimateItem['taxes'] as $tax) {
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$item->taxes()->create($tax);
}
@ -137,7 +155,7 @@ class EstimatesController extends Controller
if ($request->has('taxes')) {
foreach ($request->taxes as $tax) {
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$estimate->taxes()->create($tax);
}
@ -216,26 +234,33 @@ class EstimatesController extends Controller
return response()->json( [
'customers' => $customers,
'nextEstimateNumber' => $estimate->estimate_number,
'nextEstimateNumber' => $estimate->getEstimateNumAttribute(),
'taxes' => Tax::latest()->whereCompany($request->header('company'))->get(),
'estimate' => $estimate,
'items' => Item::whereCompany($request->header('company'))->latest()->get(),
'estimateTemplates' => EstimateTemplate::all(),
'tax_per_item' => $estimate->tax_per_item,
'discount_per_item' => $estimate->discount_per_item,
'shareable_link' => url('/estimates/pdf/'.$estimate->unique_hash)
'shareable_link' => url('/estimates/pdf/'.$estimate->unique_hash),
'estimate_prefix' => $estimate->getEstimatePrefixAttribute()
]);
}
public function update(EstimatesRequest $request, $id)
{
$estimate_number = explode("-",$request->estimate_number);
$number_attributes['estimate_number'] = $estimate_number[0].'-'.sprintf('%06d', intval($estimate_number[1]));
Validator::make($number_attributes, [
'estimate_number' => 'required|unique:estimates,estimate_number'.','.$id
])->validate();
$estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date);
$expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date);
$estimate = Estimate::find($id);
$estimate->estimate_date = $estimate_date;
$estimate->expiry_date = $expiry_date;
$estimate->estimate_number = $request->estimate_number;
$estimate->estimate_number = $number_attributes['estimate_number'];
$estimate->reference_number = $request->reference_number;
$estimate->user_id = $request->user_id;
$estimate->estimate_template_id = $request->estimate_template_id;
@ -266,7 +291,7 @@ class EstimatesController extends Controller
if (array_key_exists('taxes', $estimateItem) && $estimateItem['taxes']) {
foreach ($estimateItem['taxes'] as $tax) {
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$item->taxes()->create($tax);
}
@ -276,7 +301,7 @@ class EstimatesController extends Controller
if ($request->has('taxes')) {
foreach ($request->taxes as $tax) {
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$estimate->taxes()->create($tax);
}

View File

@ -66,14 +66,24 @@ class InvoicesController extends Controller
{
$tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company'));
$discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company'));
$nextInvoiceNumber = "INV-".Invoice::getNextInvoiceNumber();
$invoice_prefix = CompanySetting::getSetting('invoice_prefix', $request->header('company'));
$invoice_num_auto_generate = CompanySetting::getSetting('invoice_auto_generate', $request->header('company'));
$nextInvoiceNumberAttribute = null;
$nextInvoiceNumber = Invoice::getNextInvoiceNumber($invoice_prefix);
if ($invoice_num_auto_generate == "YES") {
$nextInvoiceNumberAttribute = $nextInvoiceNumber;
}
return response()->json([
'nextInvoiceNumber' => $nextInvoiceNumber,
'nextInvoiceNumberAttribute' => $nextInvoiceNumberAttribute,
'nextInvoiceNumber' => $invoice_prefix.'-'.$nextInvoiceNumber,
'items' => Item::with('taxes')->whereCompany($request->header('company'))->get(),
'invoiceTemplates' => InvoiceTemplate::all(),
'tax_per_item' => $tax_per_item,
'discount_per_item' => $discount_per_item
'discount_per_item' => $discount_per_item,
'invoice_prefix' => $invoice_prefix
]);
}
@ -85,6 +95,13 @@ class InvoicesController extends Controller
*/
public function store(Requests\InvoicesRequest $request)
{
$invoice_number = explode("-",$request->invoice_number);
$number_attributes['invoice_number'] = $invoice_number[0].'-'.sprintf('%06d', intval($invoice_number[1]));
Validator::make($number_attributes, [
'invoice_number' => 'required|unique:invoices,invoice_number'
])->validate();
$invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date);
$due_date = Carbon::createFromFormat('d/m/Y', $request->due_date);
$status = Invoice::STATUS_DRAFT;
@ -99,7 +116,7 @@ class InvoicesController extends Controller
$invoice = Invoice::create([
'invoice_date' => $invoice_date,
'due_date' => $due_date,
'invoice_number' => $request->invoice_number,
'invoice_number' => $number_attributes['invoice_number'],
'reference_number' => $request->reference_number,
'user_id' => $request->user_id,
'company_id' => $request->header('company'),
@ -128,8 +145,7 @@ class InvoicesController extends Controller
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
foreach ($invoiceItem['taxes'] as $tax) {
$tax['company_id'] = $request->header('company');
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$item->taxes()->create($tax);
}
}
@ -140,7 +156,7 @@ class InvoicesController extends Controller
foreach ($request->taxes as $tax) {
$tax['company_id'] = $request->header('company');
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$invoice->taxes()->create($tax);
}
}
@ -222,12 +238,13 @@ class InvoicesController extends Controller
])->find($id);
return response()->json([
'nextInvoiceNumber' => $invoice->invoice_number,
'nextInvoiceNumber' => $invoice->getInvoiceNumAttribute(),
'invoice' => $invoice,
'invoiceTemplates' => InvoiceTemplate::all(),
'tax_per_item' => $invoice->tax_per_item,
'discount_per_item' => $invoice->discount_per_item,
'shareable_link' => url('/invoices/pdf/'.$invoice->unique_hash)
'shareable_link' => url('/invoices/pdf/'.$invoice->unique_hash),
'invoice_prefix' => $invoice->getInvoicePrefixAttribute()
]);
}
@ -240,6 +257,13 @@ class InvoicesController extends Controller
*/
public function update(Requests\InvoicesRequest $request, $id)
{
$invoice_number = explode("-",$request->invoice_number);
$number_attributes['invoice_number'] = $invoice_number[0].'-'.sprintf('%06d', intval($invoice_number[1]));
Validator::make($number_attributes, [
'invoice_number' => 'required|unique:invoices,invoice_number'.','.$id
])->validate();
$invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date);
$due_date = Carbon::createFromFormat('d/m/Y', $request->due_date);
@ -268,7 +292,7 @@ class InvoicesController extends Controller
$invoice->invoice_date = $invoice_date;
$invoice->due_date = $due_date;
$invoice->invoice_number = $request->invoice_number;
$invoice->invoice_number = $number_attributes['invoice_number'];
$invoice->reference_number = $request->reference_number;
$invoice->user_id = $request->user_id;
$invoice->invoice_template_id = $request->invoice_template_id;
@ -292,7 +316,6 @@ class InvoicesController extends Controller
foreach ($oldTaxes as $oldTax) {
Tax::destroy($oldTax['id']);
}
foreach ($invoiceItems as $invoiceItem) {
$invoiceItem['company_id'] = $request->header('company');
$item = $invoice->items()->create($invoiceItem);
@ -300,8 +323,7 @@ class InvoicesController extends Controller
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
foreach ($invoiceItem['taxes'] as $tax) {
$tax['company_id'] = $request->header('company');
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$item->taxes()->create($tax);
}
}
@ -312,7 +334,7 @@ class InvoicesController extends Controller
foreach ($request->taxes as $tax) {
$tax['company_id'] = $request->header('company');
if ($tax['amount']) {
if (gettype($tax['amount']) !== "NULL") {
$invoice->taxes()->create($tax);
}
}

View File

@ -235,6 +235,45 @@ class OnboardingController extends Controller
);
}
$invoices = [
'invoice_auto_generate' => 'YES',
'invoice_prefix' => 'INV'
];
foreach ($invoices as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$estimates = [
'estimate_prefix' => 'EST',
'estimate_auto_generate' => 'YES'
];
foreach ($estimates as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$payments = [
'payment_prefix' => 'PAY',
'payment_auto_generate' => 'YES'
];
foreach ($payments as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$colors = [
'primary_text_color' => '#5851D8',
'heading_text_color' => '#595959',
@ -271,7 +310,14 @@ class OnboardingController extends Controller
if (file_exists($path)) {
file_put_contents($path, str_replace(
'PROXY_OAUTH_CLIENT_SECRET='.config('auth.proxy.client_secret'), 'PROXY_OAUTH_CLIENT_SECRET='.$client->secret, file_get_contents($path)
'PROXY_OAUTH_CLIENT_SECRET='.config('auth.proxy.client_secret'),
'PROXY_OAUTH_CLIENT_SECRET='.$client->secret,
file_get_contents($path)
));
file_put_contents($path, str_replace(
'APP_DEBUG=true',
'APP_DEBUG=false',
file_get_contents($path)
));
}

View File

@ -10,6 +10,7 @@ use Carbon\Carbon;
use function MongoDB\BSON\toJSON;
use Crater\User;
use Crater\Http\Requests\PaymentRequest;
use Validator;
class PaymentController extends Controller
{
@ -50,13 +51,24 @@ class PaymentController extends Controller
*/
public function create(Request $request)
{
$nextPaymentNumber = 'PAY-'.Payment::getNextPaymentNumber();
$payment_prefix = CompanySetting::getSetting('payment_prefix', $request->header('company'));
$payment_num_auto_generate = CompanySetting::getSetting('payment_auto_generate', $request->header('company'));
$nextPaymentNumberAttribute = null;
$nextPaymentNumber = Payment::getNextPaymentNumber($payment_prefix);
if ($payment_num_auto_generate == "YES") {
$nextPaymentNumberAttribute = $nextPaymentNumber;
}
return response()->json([
'customers' => User::where('role', 'customer')
->whereCompany($request->header('company'))
->get(),
'nextPaymentNumber' => $nextPaymentNumber
'nextPaymentNumberAttribute' => $nextPaymentNumberAttribute,
'nextPaymentNumber' => $payment_prefix.'-'.$nextPaymentNumber,
'payment_prefix' => $payment_prefix
]);
}
@ -68,6 +80,13 @@ class PaymentController extends Controller
*/
public function store(PaymentRequest $request)
{
$payment_number = explode("-",$request->payment_number);
$number_attributes['payment_number'] = $payment_number[0].'-'.sprintf('%06d', intval($payment_number[1]));
Validator::make($number_attributes, [
'payment_number' => 'required|unique:payments,payment_number'
])->validate();
$payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date);
if ($request->has('invoice_id') && $request->invoice_id != null) {
@ -90,7 +109,7 @@ class PaymentController extends Controller
$payment = Payment::create([
'payment_date' => $payment_date,
'payment_number' => $request->payment_number,
'payment_number' => $number_attributes['payment_number'],
'user_id' => $request->user_id,
'company_id' => $request->header('company'),
'invoice_id' => $request->invoice_id,
@ -135,7 +154,8 @@ class PaymentController extends Controller
'customers' => User::where('role', 'customer')
->whereCompany($request->header('company'))
->get(),
'nextPaymentNumber' => $payment->payment_number,
'nextPaymentNumber' => $payment->getPaymentNumAttribute(),
'payment_prefix' => $payment->getPaymentPrefixAttribute(),
'payment' => $payment,
'invoices' => $invoices
]);
@ -150,6 +170,13 @@ class PaymentController extends Controller
*/
public function update(PaymentRequest $request, $id)
{
$payment_number = explode("-",$request->payment_number);
$number_attributes['payment_number'] = $payment_number[0].'-'.sprintf('%06d', intval($payment_number[1]));
Validator::make($number_attributes, [
'payment_number' => 'required|unique:payments,payment_number'.','.$id
])->validate();
$payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date);
$payment = Payment::find($id);
@ -178,7 +205,7 @@ class PaymentController extends Controller
}
$payment->payment_date = $payment_date;
$payment->payment_number = $request->payment_number;
$payment->payment_number = $number_attributes['payment_number'];
$payment->user_id = $request->user_id;
$payment->invoice_id = $request->invoice_id;
$payment->payment_mode = $request->payment_mode;

View File

@ -25,7 +25,6 @@ class EstimatesRequest extends FormRequest
$rules = [
'estimate_date' => 'required',
'expiry_date' => 'required',
'estimate_number' => 'required|unique:estimates,estimate_number',
'user_id' => 'required',
'discount' => 'required',
'discount_val' => 'required',
@ -41,10 +40,6 @@ class EstimatesRequest extends FormRequest
'items.*.price' => 'required'
];
if ($this->getMethod() == 'PUT') {
$rules['estimate_number'] = $rules['estimate_number'].','.$this->get('id');
}
return $rules;
}
}

View File

@ -25,7 +25,6 @@ class InvoicesRequest extends FormRequest
$rules = [
'invoice_date' => 'required',
'due_date' => 'required',
'invoice_number' => 'required|unique:invoices,invoice_number',
'user_id' => 'required',
'discount' => 'required',
'discount_val' => 'required',
@ -41,10 +40,6 @@ class InvoicesRequest extends FormRequest
'items.*.price' => 'required'
];
if ($this->getMethod() == 'PUT') {
$rules['invoice_number'] = $rules['invoice_number'].','.$this->get('id');
}
return $rules;
}
}

View File

@ -24,15 +24,10 @@ class PaymentRequest extends FormRequest
{
$rules = [
'payment_date' => 'required',
'payment_number' => 'required|unique:payments,payment_number',
'user_id' => 'required',
'amount' => 'required',
];
if ($this->getMethod() == 'PUT') {
$rules['payment_number'] = $rules['payment_number'].','.$this->route('payment');
}
return $rules;
}
}

View File

@ -30,7 +30,7 @@ class ProfileRequest extends FormRequest
case 'POST':
return [
'name' => 'required',
'password' => 'required',
'password' => 'required|min:8',
'address_street_1' => 'max:255',
'address_street_2' => 'max:255',
'email' => [

View File

@ -66,10 +66,14 @@ class Invoice extends Model
'formattedDueDate'
];
public static function getNextInvoiceNumber()
public static function getNextInvoiceNumber($value)
{
// Get the last created order
$lastOrder = Invoice::orderBy('created_at', 'desc')->first();
$lastOrder = Invoice::where('invoice_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
@ -143,10 +147,15 @@ class Invoice extends Model
public function getInvoiceNumAttribute()
{
$position = $this->strposX($this->invoice_number, "-", 2) + 1;
$position = $this->strposX($this->invoice_number, "-", 1) + 1;
return substr($this->invoice_number, $position);
}
public function getInvoicePrefixAttribute () {
$prefix = explode("-", $this->invoice_number)[0];
return $prefix;
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);

View File

@ -6,7 +6,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Database\Schema\Blueprint;
use Crater\Listeners\Updates\Listener;
use Crater\Listeners\Updates\v2\Version200;
use Crater\Events\UpdateFinished;
use Crater\Setting;
use Crater\Address;

View File

@ -0,0 +1,64 @@
<?php
namespace Crater\Listeners\Updates\v2;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Crater\Events\UpdateFinished;
use Crater\Listeners\Updates\Listener;
use Crater\Setting;
use Crater\CompanySetting;
class Version210 extends Listener
{
const VERSION = '2.1.0';
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(UpdateFinished $event)
{
if ($this->isListenerFired($event)) {
return;
}
// Add initial auto generate value
$this->addAutoGenerateSettings();
// Update Crater app version
Setting::setSetting('version', static::VERSION);
}
private function addAutoGenerateSettings()
{
$settings = [
'invoice_auto_generate' => 'YES',
'invoice_prefix' => 'INV',
'estimate_prefix' => 'EST',
'estimate_auto_generate' => 'YES',
'payment_prefix' => 'PAY',
'payment_auto_generate' => 'YES'
];
foreach ($settings as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
auth()->user()->company->id
);
}
}
}

View File

@ -32,10 +32,34 @@ class Payment extends Model
'formattedPaymentDate'
];
public static function getNextPaymentNumber()
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {
return strpos($haystack, $needle);
} elseif ($number > '1') {
return strpos(
$haystack,
$needle,
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
);
} else {
return error_log('Error: Value for parameter $number is out of range');
}
}
public function getPaymentNumAttribute()
{
$position = $this->strposX($this->payment_number, "-", 1) + 1;
return substr($this->payment_number, $position);
}
public static function getNextPaymentNumber($value)
{
// Get the last created order
$payment = Payment::orderBy('created_at', 'desc')->first();
$payment = Payment::where('payment_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$payment) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
@ -54,6 +78,13 @@ class Payment extends Model
return sprintf('%06d', intval($number) + 1);
}
public function getPaymentPrefixAttribute ()
{
$prefix= explode("-",$this->payment_number)[0];
return $prefix;
}
public function invoice()
{
return $this->belongsTo(Invoice::class);

View File

@ -9,6 +9,7 @@ use Crater\Listeners\Updates\v1\Version110;
use Crater\Listeners\Updates\v2\Version200;
use Crater\Listeners\Updates\v2\Version201;
use Crater\Listeners\Updates\v2\Version202;
use Crater\Listeners\Updates\v2\Version210;
class EventServiceProvider extends ServiceProvider
{
@ -23,6 +24,7 @@ class EventServiceProvider extends ServiceProvider
Version200::class,
Version201::class,
Version202::class,
Version210::class,
],
Registered::class => [
SendEmailVerificationNotification::class,

View File

@ -61,7 +61,7 @@ class EnvironmentManager
} catch (Exception $e) {
return [
'error' => $e->getMessage()
'error_message' => $e->getMessage()
];
}

View File

@ -41,10 +41,14 @@ function clean_slug($string)
* @param $money
* @return formated_money
*/
function format_money_pdf($money)
function format_money_pdf($money, $currency = null)
{
$money = $money / 100;
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', 1));
if (!$currency) {
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', 1));
}
$format_money = number_format(
$money,
$currency->precision,

View File

@ -9,6 +9,6 @@ return [
|
*/
'version' => '2.0.2',
'version' => '2.1.0',
];

244
config/dompdf.php Normal file
View File

@ -0,0 +1,244 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Settings
|--------------------------------------------------------------------------
|
| Set some default values. It is possible to add all defines that can be set
| in dompdf_config.inc.php. You can also override the entire config file.
|
*/
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait',
'defines' => array(
/**
* The location of the DOMPDF font directory
*
* The location of the directory where DOMPDF will store fonts and font metrics
* Note: This directory must exist and be writable by the webserver process.
* *Please note the trailing slash.*
*
* Notes regarding fonts:
* Additional .afm font metrics can be added by executing load_font.php from command line.
*
* Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must
* be embedded in the pdf file or the PDF may not display correctly. This can significantly
* increase file size unless font subsetting is enabled. Before embedding a font please
* review your rights under the font license.
*
* Any font specification in the source HTML is translated to the closest font available
* in the font directory.
*
* The pdf standard "Base 14 fonts" are:
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats.
*/
"font_dir" => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/**
* The location of the DOMPDF font cache directory
*
* This directory contains the cached font metrics for the fonts used by DOMPDF.
* This directory can be the same as DOMPDF_FONT_DIR
*
* Note: This directory must exist and be writable by the webserver process.
*/
"font_cache" => storage_path('fonts/'),
/**
* The location of a temporary directory.
*
* The directory specified must be writeable by the webserver process.
* The temporary directory is required to download remote images and when
* using the PFDLib back end.
*/
"temp_dir" => sys_get_temp_dir(),
/**
* ==== IMPORTANT ====
*
* dompdf's "chroot": Prevents dompdf from accessing system files or other
* files on the webserver. All local files opened by dompdf must be in a
* subdirectory of this directory. DO NOT set it to '/' since this could
* allow an attacker to use dompdf to read any files on the server. This
* should be an absolute path.
* This is only checked on command line call by dompdf.php, but not by
* direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/
"chroot" => realpath(base_path()),
/**
* Whether to enable font subsetting or not.
*/
"enable_font_subsetting" => false,
/**
* The PDF rendering backend to use
*
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
* fall back on CPDF. 'GD' renders PDFs to graphic files. {@link
* Canvas_Factory} ultimately determines which rendering class to instantiate
* based on this setting.
*
* Both PDFLib & CPDF rendering backends provide sufficient rendering
* capabilities for dompdf, however additional features (e.g. object,
* image and font support, etc.) differ between backends. Please see
* {@link PDFLib_Adapter} for more information on the PDFLib backend
* and {@link CPDF_Adapter} and lib/class.pdf.php for more information
* on CPDF. Also see the documentation for each backend at the links
* below.
*
* The GD rendering backend is a little different than PDFLib and
* CPDF. Several features of CPDF and PDFLib are not supported or do
* not make any sense when creating image files. For example,
* multiple pages are not supported, nor are PDF 'objects'. Have a
* look at {@link GD_Adapter} for more information. GD support is
* experimental, so use it at your own risk.
*
* @link http://www.pdflib.com
* @link http://www.ros.co.nz/pdf
* @link http://www.php.net/image
*/
"pdf_backend" => "CPDF",
/**
* PDFlib license key
*
* If you are using a licensed, commercial version of PDFlib, specify
* your license key here. If you are using PDFlib-Lite or are evaluating
* the commercial version of PDFlib, comment out this setting.
*
* @link http://www.pdflib.com
*
* If pdflib present in web server and auto or selected explicitely above,
* a real license code must exist!
*/
//"DOMPDF_PDFLIB_LICENSE" => "your license key here",
/**
* html target media view which should be rendered into pdf.
* List of types and parsing rules for future extensions:
* http://www.w3.org/TR/REC-html40/types.html
* screen, tty, tv, projection, handheld, print, braille, aural, all
* Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3.
* Note, even though the generated pdf file is intended for print output,
* the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here.
*/
"default_media_type" => "screen",
/**
* The default paper size.
*
* North America standard is "letter"; other countries generally "a4"
*
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/
"default_paper_size" => "a4",
/**
* The default font family
*
* Used if no suitable fonts can be found. This must exist in the font folder.
* @var string
*/
"default_font" => "DejaVu Sans",
/**
* Image DPI setting
*
* This setting determines the default DPI setting for images and fonts. The
* DPI may be overridden for inline images by explictly setting the
* image's width & height style attributes (i.e. if the image's native
* width is 600 pixels and you specify the image's width as 72 points,
* the image will have a DPI of 600 in the rendered PDF. The DPI of
* background images can not be overridden and is controlled entirely
* via this parameter.
*
* For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
* If a size in html is given as px (or without unit as image size),
* this tells the corresponding size in pt.
* This adjusts the relative sizes to be similar to the rendering of the
* html page in a reference browser.
*
* In pdf, always 1 pt = 1/72 inch
*
* Rendering resolution of various browsers in px per inch:
* Windows Firefox and Internet Explorer:
* SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:?
* Linux Firefox:
* about:config *resolution: Default:96
* (xorg screen dimension in mm and Desktop font dpi settings are ignored)
*
* Take care about extra font/image zoom factor of browser.
*
* In images, <img> size in pixel attribute, img css style, are overriding
* the real image dimension in px for rendering.
*
* @var int
*/
"dpi" => 96,
/**
* Enable inline PHP
*
* If this setting is set to true then DOMPDF will automatically evaluate
* inline PHP contained within <script type="text/php"> ... </script> tags.
*
* Enabling this for documents you do not trust (e.g. arbitrary remote html
* pages) is a security risk. Set this option to false if you wish to process
* untrusted documents.
*
* @var bool
*/
"enable_php" => false,
/**
* Enable inline Javascript
*
* If this setting is set to true then DOMPDF will automatically insert
* JavaScript code contained within <script type="text/javascript"> ... </script> tags.
*
* @var bool
*/
"enable_javascript" => true,
/**
* Enable remote file access
*
* If this setting is set to true, DOMPDF will access remote sites for
* images and CSS files as required.
* This is required for part of test case www/test/image_variants.html through www/examples.php
*
* Attention!
* This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and
* allowing remote access to dompdf.php or on allowing remote html code to be passed to
* $dompdf = new DOMPDF(, $dompdf->load_html(...,
* This allows anonymous users to download legally doubtful internet content which on
* tracing back appears to being downloaded by your server, or allows malicious php code
* in remote html pages to be executed by your server with your account privileges.
*
* @var bool
*/
"enable_remote" => true,
/**
* A ratio applied to the fonts height to be more like browsers' line height
*/
"font_height_ratio" => 1.1,
/**
* Use the more-than-experimental HTML5 Lib parser
*/
"enable_html5_parser" => true,
),
);

View File

@ -20,9 +20,9 @@ class CreateMediaTable extends Migration
$table->string('mime_type')->nullable();
$table->string('disk');
$table->unsignedInteger('size');
$table->json('manipulations');
$table->json('custom_properties');
$table->json('responsive_images');
$table->text('manipulations');
$table->text('custom_properties');
$table->text('responsive_images');
$table->unsignedInteger('order_column')->nullable();
$table->nullableTimestamps();
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
{
"/assets/js/app.js": "/assets/js/app.js?id=36ab3529ebffd4f0624b",
"/assets/css/crater.css": "/assets/css/crater.css?id=108e3a8d009e7d38018c"
"/assets/js/app.js": "/assets/js/app.js?id=a9f802b3fe774e87bf0c",
"/assets/css/crater.css": "/assets/css/crater.css?id=193e5770a0e7a8604f35"
}

View File

@ -29,7 +29,7 @@ Web Application is made using Laravel & VueJS while the Mobile Apps are built us
## Mobile Apps
- [Android](https://play.google.com/store/apps/details?id=com.craterapp.app)
- IOS - Coming Soon
- [IOS](https://apps.apple.com/app/id1489169767)
- [Source](https://github.com/bytefury/crater-mobile)
## Discord
@ -62,7 +62,7 @@ Crater is a product of [Bytefury](https://bytefury.com)
**Special thanks to:**
* [Birkhoff Lee](https://github.com/BirkhoffLee)
* [Hassan A. Ba Abdullah](https://github.com/hsnapps)
## Translate
Help us translate on [Transifex](https://www.transifex.com/bytefury/crater-invoice)

View File

@ -0,0 +1,71 @@
<template>
<div class="base-prefix-input" @click="focusInput">
<font-awesome-icon v-if="icon" :icon="icon" class="icon" />
<p class="prefix-label"><span class="mr-1">{{ prefix }}</span>-</p>
<input
ref="basePrefixInput"
v-model="inputValue"
:type="type"
class="prefix-input-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
@keydown="handleKeyDownEnter"
@blur="handleFocusOut"
>
</div>
</template>
<script>
export default {
props: {
prefix: {
type: String,
default: null,
required: true
},
icon: {
type: String,
default: null
},
value: {
type: [String, Number, File],
default: ''
},
type: {
type: String,
default: 'text'
}
},
data () {
return {
inputValue: this.value
}
},
watch: {
'value' () {
this.inputValue = this.value
}
},
methods: {
focusInput () {
this.$refs.basePrefixInput.focus()
},
handleInput (e) {
this.$emit('input', this.inputValue)
},
handleChange (e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.inputValue)
},
handleKeyDownEnter (e) {
this.$emit('keydown', e, this.inputValue)
},
handleFocusOut (e) {
this.$emit('blur', this.inputValue)
}
}
}
</script>

View File

@ -8,6 +8,7 @@ import BaseTextArea from './BaseTextArea.vue'
import BaseSelect from './base-select/BaseSelect.vue'
import BaseLoader from './BaseLoader.vue'
import BaseCustomerSelect from './BaseCustomerSelect.vue'
import BasePrefixInput from './BasePrefixInput.vue'
import BasePopup from './popup/BasePopup.vue'
import CustomerSelectPopup from './popup/CustomerSelectPopup.vue'
@ -23,6 +24,7 @@ Vue.component('base-input', BaseInput)
Vue.component('base-switch', BaseSwitch)
Vue.component('base-text-area', BaseTextArea)
Vue.component('base-loader', BaseLoader)
Vue.component('base-prefix-input', BasePrefixInput)
Vue.component('table-component', TableComponent)
Vue.component('table-column', TableColumn)

View File

@ -55,6 +55,7 @@
v-model="currency"
:options="currencies"
:searchable="true"
:allow-empty="false"
:show-labels="false"
:placeholder="$t('customers.select_currency')"
label="name"

View File

@ -124,7 +124,7 @@ export default {
},
percent: {
required,
between: between(0.10, 100)
between: between(0, 100)
},
description: {
maxLength: maxLength(255)

View File

@ -0,0 +1,880 @@
{
"navigation": {
"dashboard": "الرئيسية",
"customers": "العملاء",
"items": "الأصناف",
"invoices": "الفواتير",
"expenses": "النفقات",
"estimates": "التقديرات",
"payments": "المدفوعات",
"reports": "التقارير",
"settings": "الإعدادات",
"logout": "خروج"
},
"general": {
"view_pdf": "عرض PDF",
"download_pdf": "تنزيل PDF",
"save": "حفظ",
"cancel": "إلغاء الأمر",
"update": "تحديث",
"download": "تنزيل",
"from_date": "من تاريخ",
"to_date": "إلى تاريخ",
"from": "من",
"to": "إلى",
"go_back": "إلى الخلف",
"back_to_login": "العودة إلى تسجيل الدخول؟",
"home": "الرئيسية",
"filter": "تصفية",
"delete": "حذف",
"edit": "تعديل",
"view": "عرض",
"add_new_item": "إضافة صنف جديد",
"clear_all": "مسح الكل",
"showing": "عرض",
"of": "من",
"actions": "العمليات",
"subtotal": "المجموع الفرعي",
"discount": "خصم",
"fixed": "ثابت",
"percentage": "نسبة",
"tax": "ضريبة",
"total_amount": "المبلغ الإجمالي",
"bill_to": "مطلوب من",
"ship_to": "يشحن إلى",
"due": "واجبة السداد",
"draft": "مسودة",
"sent": "مرسلة",
"all": "الكل",
"select_all": "تحديد الل",
"choose_file": "اضغط هنا لاختيار ملف",
"choose_template": "اختيار القالب",
"choose": "اختر",
"remove": "إزالة",
"powered_by": "تصميم",
"bytefury": "باترفوري",
"select_a_status": "اختر الحالة",
"select_a_tax": "اختر الضريبة",
"search": "بحث",
"are_you_sure": "هل أنت متأكد?",
"list_is_empty": "القائمة فارغة.",
"no_tax_found": "لا يوجد ضريبة!",
"four_zero_four": "404",
"you_got_lost": "عفواً! يبدو أنك قد تهت!",
"go_home": "عودة إلى الرئيسية",
"setting_updated": "تم تحديث الإعدادات بنجاح",
"select_state": "اختر الولاية/المنطقة",
"select_country": "اختر الدولة",
"select_city": "اختر المدينة",
"street_1": "عنوان الشارع 1",
"street_2": "عنوان الشارع 2",
"action_failed": "فشلت العملية"
},
"dashboard": {
"select_year": "اختر السنة",
"cards": {
"due_amount": "المبلغ المطلوب",
"customers": "العملاء",
"invoices": "الفواتير",
"estimates": "التقديرات"
},
"chart_info": {
"total_sales": "المبيعات",
"total_receipts": "إجمالي الدخل",
"total_expense": "النفقات",
"net_income": "صافي الدخل",
"year": "اختر السنة"
},
"weekly_invoices": {
"title": "الفواتير الأسبوعية"
},
"monthly_chart": {
"title": "المبيعات والنفقات"
},
"recent_invoices_card": {
"title": "فواتير مستحقة",
"due_on": "مستحقة في",
"customer": "العميل",
"amount_due": "المبلغ المطلوب",
"actions": "العمليات",
"view_all": "عرض الكل"
},
"recent_estimate_card": {
"title": "أحدث التقديرات",
"date": "التاريخ",
"customer": "العميل",
"amount_due": "المبلغ المطلوب",
"actions": "العمليات",
"view_all": "عرض الكل"
}
},
"tax_types": {
"name": "الاسم",
"description": "الوصف",
"percent": "Percent",
"compound_tax": "Compound Tax"
},
"customers": {
"title": "العملاء",
"add_customer": "إضافة عميل",
"contacts_list": "قائمة العملاء",
"name": "الاسم",
"display_name": "اسم العرض",
"primary_contact_name": "اسم التواصل الرئيسي",
"contact_name": "اسم تواصل آخر",
"amount_due": "المبلغ المطلوب",
"email": "البريد الإلكتروني",
"address": "العنوان",
"phone": "الهاتف",
"website": "موقع الإنترنت",
"country": "الدولة",
"state": "الولاية/المنطقة",
"city": "المدينة",
"zip_code": "الرمز البريدي",
"added_on": "أضيف في",
"action": "إجراء",
"password": "كلمة المرور",
"street_number": "رقم الشارع",
"primary_currency": "العملة الرئيسية",
"add_new_customer": "إضافة عميل جديد",
"save_customer": "حفظ العميل",
"update_customer": "تحديث بيانات العميل",
"customer": "عميل | عملاء",
"new_customer": "عميل جديد",
"edit_customer": "تعديل عميل",
"basic_info": "معلوات أساسية",
"billing_address": "عنوان الفوترة",
"shipping_address": "عنوان الشحن",
"copy_billing_address": "نسخ من عنوان الفوترة",
"no_customers": "لا يوجد عملاء حتى الآن!",
"no_customers_found": "لم يتم الحصول على عملاء!",
"list_of_customers": "سوف يحتوي هذا القسم على قائمة العملاء.",
"primary_display_name": "اسم العرض الرئيسي",
"select_currency": "اختر العملة",
"select_a_customer": "اختر العميل",
"type_or_click": "اكتب أو اضغط للاختيار",
"confirm_delete": "لن تكون قادراً على استرجاع هذا العميل | لن تكون قادراً على استرجاع هؤلاء العملاء",
"created_message": "تم إنشاء العملاء بنجاح",
"updated_message": "تم تحديث العملاء بنجاح",
"deleted_message": "تم حذف العملاء بنجاح | تم حذف العميل بنجاح"
},
"items": {
"title": "الأصناف",
"items_list": "قائمة الأصناف",
"name": "الاسم",
"unit": "الوحدة",
"description": "الوصف",
"added_on": "أضيف في",
"price": "السعر",
"date_of_creation": "تاريخ الإنشاء",
"action": "إجراء",
"add_item": "إضافة صنف",
"save_item": "حفظ الصنف",
"update_item": "تحديث الصنف",
"item": "صنف | أصناف",
"add_new_item": "إضافة صنف جديد",
"new_item": "جديد صنف",
"edit_item": "تحديث صنف",
"no_items": "لا يوجد أصناف حتى الآن!",
"list_of_items": "هذا القسم سوف يحتوي على قائمة الأصناف.",
"select_a_unit": "اختر الوحدة",
"item_attached_message": "لا يمكن حذف الصنف قيد الاستخدام",
"confirm_delete": "لن تتمكن من استرجاع هذا الصنف | لن تتمكن من استرجاع هذه الأصناف",
"created_message": "تم إنشاء الصنف بنجاح",
"updated_message": "تم تحديث الصنف بنجاح",
"deleted_message": "تم حذف الصنف بنجاح | تم حذف الأصناف بنجاح"
},
"estimates": {
"title": "التقديرات",
"estimate": "تقدير | تقديرات",
"estimates_list": "قائمة التقديرات",
"days": "{days} أيام",
"months": "{months} أشهر",
"years": "{years} سنوات",
"all": "الكل",
"paid": "مدفوع",
"unpaid": "غير مدفوع",
"customer": "العميل",
"ref_no": "رقم المرجع.",
"number": "الرقم",
"amount_due": "المبلغ المطلوب",
"partially_paid": "مدفوع جزئيا",
"total": "الإجمالي",
"discount": "الخصم",
"sub_total": "حاصل الجمع",
"estimate_number": "رقم تقدير",
"ref_number": "رقم المرجع",
"contact": "تواصل",
"add_item": "إضافة صنف",
"date": "تاريخ",
"due_date": "تاريخ الاستحقاق",
"expiry_date": "تاريخ الصلاحية",
"status": "الحالة",
"add_tax": "إضافة ضرية",
"amount": "المبلغ المطلوب",
"action": "إجراء",
"notes": "ملاحظات",
"tax": "ضريبة",
"estimate_template": "قالب",
"convert_to_invoice": "تحويل إلى فاتورة",
"mark_as_sent": "تحديد كمرسل",
"send_estimate": "إرسال التقدير",
"record_payment": "تسجيل مدفوات",
"add_estimate": "إضافة تقدير",
"save_estimate": "حفظ التقدير",
"confirm_conversion": "هل تريد تحويل هذا التقدير إلى فاتورة؟",
"conversion_message": "تم إنشاء الفاتورة بنجاح",
"confirm_send_estimate": "سيتم إرسال هذا التقدير بالبريد الإلكتروني إلى العميل",
"confirm_mark_as_sent": "سيتم التحديد كمرسل على هذا التقدير",
"confirm_mark_as_accepted": "سيتم التحديد كمقبول على هذا التقدير",
"confirm_mark_as_rejected": "سيتم التحديد كمرفوض على هذا التقدير",
"no_matching_estimates": "لا يوجد تقديرات مطابقة!",
"mark_as_sent_successfully": "تم التحديد كمرسل بنجاح",
"send_estimate_successfully": "تم إرسال التقدير بنجاح",
"errors": {
"required": "حقل مطلوب"
},
"accepted": "مقبول",
"sent": "مرسل",
"draft": "مسودة",
"declined": "مرفوض",
"new_estimate": "تقدير جديد",
"add_new_estimate": "إضافة تقدير جديد",
"update_Estimate": "تحديث تقدير",
"edit_estimate": "تعديل التقدير",
"items": "الأصناف",
"Estimate": "تقدير | تقديرات",
"add_new_tax": "إضافة ضريبة جديدة",
"no_estimates": "لا يوجد تقديرات حالياً!",
"list_of_estimates": "هذا القسم سوف يحتوي على التقديرات.",
"mark_as_rejected": "تحديد كمرفوض",
"mark_as_accepted": "تحديد كمقروء",
"marked_as_accepted_message": "تحديد التقدير كمقبول",
"marked_as_rejected_message": "تحديد التقدير كمرفوض",
"confirm_delete": "لن تستطيع استرجاع هذا التقدير | لن تستطيع إستعادة هذه التقديرات",
"created_message": "تم إنشاء التقدير بنجاح",
"updated_message": "تم تحديث التقدير بنجاح",
"deleted_message": "تم حذف التقدير بنجاح | تم حذف التقديرات بنجاح",
"user_email_does_not_exist": "البريد الالكتروني للمستخدم غير موجود",
"something_went_wrong": "خطأ غير معروف!",
"item": {
"title": "اسم الصنف",
"description": "الوصف",
"quantity": "الكمية",
"price": "السعر",
"discount": "الخصم",
"total": "الإجمالي",
"total_discount": "مجموع الخصم",
"sub_total": "حاصل الجمع",
"tax": "الضرية",
"amount": "المبلغ المطلوب",
"select_an_item": "اكتب أو اختر الصنف",
"type_item_description": "اكتب وصف الصنف (اختياري)"
}
},
"invoices": {
"title": "الفواتير",
"invoices_list": "قائمة الفواتير",
"days": "{days} أيام",
"months": "{months} أشهر",
"years": "{years} سنوات",
"all": "الكل",
"paid": "مدفوع",
"unpaid": "غير مدفوع",
"customer": "العميل",
"paid_status": "حالة الدفع",
"ref_no": "رقم المرجع.",
"number": "الرقم",
"amount_due": "المبلغ المطلوب",
"partially_paid": "مدفوع جزئياً",
"total": "الإجمالي",
"discount": "الخصم",
"sub_total": "حاصل الجمع",
"invoice": "فاتورة | فواتير",
"invoice_number": "رقم الفاتورة",
"ref_number": "رقم المرجع",
"contact": "تواصل",
"add_item": "إضافة صنف",
"date": "التاريخ",
"due_date": "تاريخ الاستحقاق",
"status": "الحالة",
"add_tax": "إضافة ضريبة",
"amount": "المبلغ المطلوب",
"action": "إجراء",
"notes": "ملاحظات",
"view": "عرض",
"send_invoice": "إرسال الفاتورة",
"invoice_template": "قالب الفاتورة",
"template": "قالب",
"mark_as_sent": "تحديد كمرسل",
"confirm_send_invoice": "سيتم إرسال هذه الفاتورة بالبريد الألكتروني إلى العميل",
"invoice_mark_as_sent": "سيتم تحديد هذه الفاتورة كمرسلة",
"confirm_send": "سيتم إرسال هذه الفاتورة بالبريد الألكتروني إلى العميل",
"invoice_date": "تاريخ الفاتورة",
"record_payment": "تسجيل مدفوعات",
"add_new_invoice": "إضافة فاتورة جديدة",
"update_expense": "تحديث المصروفات",
"edit_invoice": "تعديل الفاتورة",
"new_invoice": "فاتورة جديدة",
"save_invoice": "حفظ الفاتورة",
"update_invoice": "تحديث الفاتورة",
"add_new_tax": "إضافة ضريبة جديدة",
"no_invoices": "لا يوجد فواتير حتى الآن!",
"list_of_invoices": "قائمة الفواتير .",
"select_invoice": "اختر الفاتورة",
"no_matching_invoices": "لا يوجد فواتير مطابقة!",
"mark_as_sent_successfully": "تم تحديد الفاتورة كمرسلة بنجاح",
"send_invoice_successfully": "تم إرسال الفاتورة بنجاح",
"item": {
"title": "اسم الصنف",
"description": "الوصف",
"quantity": "الكمية",
"price": "السعر",
"discount": "الخصم",
"total": "الإجمالي",
"total_discount": "إجمالي الخصم",
"sub_total": "حاصل الجمع",
"tax": "الضريبة",
"amount": "المبلغ المطلوب",
"select_an_item": "اكتب أو انقر لاختيار صنف",
"type_item_description": "وصف الصنف (اختياري)"
},
"payment_attached_message": "هناك مدفوعات مرتبطة بالفعل بإحدى الفواتير المحددة. تأكد من حذف المدفوعات المرتبطة أولاً قبل حذف الفاتورة.",
"confirm_delete": "لن تتمكن من استرجاع الفاتورة بعد هذه الإجراء | لن تتمكن من استرجاع الفواتير بعد هذا الإجراء",
"created_message": "تم إنشاء الفاتورة بنجاح",
"updated_message": "تم تحديث الفاتورة بنجاح",
"deleted_message": "تم حذف الفاتورة بنجاح | تم حذف الفواتير بنجاح",
"marked_as_sent_message": "تم إرسال الفاتورة بنجاح",
"user_email_does_not_exist": "البريد الإلكتروني للمستخدم غير موجود!",
"something_went_wrong": "خطأ غير معروف!",
"invalid_due_amount_message": "المبلغ النهائي للفاتورة لا يمكن أن يكون أقل من المبلغ المطلوب لها. رجاءاً حدث الفاتورة أو قم بحذف المدفوعات المرتبطة بها للاستمرار."
},
"credit_notes": {
"title": "ملاحظات إئتمانية",
"credit_notes_list": "قائمة الملاحظات الإئتمانية",
"credit_notes": "ملاحظات إئتمانية",
"contact": "تواصل",
"date": "التاريخ",
"amount": "المبلغ المطلوب",
"action": "إجراء",
"credit_number": "الرقم الإئتماني",
"notes": "ملاحظات",
"confirm_delete": "هل تريد حفذف هذه الملاحظة الإئتمانية؟",
"item": {
"title": "اسم الصنف",
"description": "الوصف",
"quantity": "الكمية",
"price": "السعر",
"discount": "الخصم",
"total": "الإجمالي",
"total_discount": "إجمالي الخصم",
"sub_total": "حاصل الجمع",
"tax": "الضريبة"
}
},
"payments": {
"title": "المدفوعات",
"payments_list": "قائمة المدفوعات",
"record_payment": "تسجيل دفعة",
"customer": "العميل",
"date": "التاريخ",
"amount": "المبلغ المطلوب",
"action": "إجراء",
"payment_number": "رقم الدفعة",
"payment_mode": "نوع الدفعة",
"invoice": "الفاتورة",
"note": "ملاحظة",
"add_payment": "إضافة دفعة",
"new_payment": "دفعة جديدة",
"edit_payment": "تعديل الدفعة",
"view_payment": "عرض الدفعة",
"add_new_payment": "إضافة دفعة جديدة",
"save_payment": "حفظ الدفعة",
"update_payment": "تحديث الدفعة",
"payment": "دفعة | مدفوعات",
"no_payments": "لا يوجد مدفوعات حتى الآن!",
"list_of_payments": "سوف تحتوي هذه القائمة على مدفوعات الفواتير.",
"select_payment_mode": "اختر طريقة الدفع",
"confirm_delete": "لن تكون قادر على استرجاع هذه الدفعة | لن تكون قادراً على استرجاع هذه المدفوعات",
"created_message": "تم إنشاء الدفعة بنجاح",
"updated_message": "تم تحديث الدفعة بنجاح",
"deleted_message": "تم حذف الدفعة بنجاح | تم حذف المدفوعات بنجاح",
"invalid_amount_message": "قيمة الدفعة غير صحيحة!"
},
"expenses": {
"title": "النفقات",
"expenses_list": "قائمة النفقات",
"expense_title": "Title",
"contact": "تواصل",
"category": "الفئة",
"from_date": "من تاريخ",
"to_date": "حتى تاريخ",
"expense_date": "التاريخ",
"description": "الوصف",
"receipt": "سند القبض",
"amount": "المبلغ المطلوب",
"action": "إجراء",
"note": "ملاحظة",
"category_id": "رمز الفئة",
"date": "تاريخ النفقات",
"add_expense": "أضف نفقات",
"add_new_expense": "أضف نفقات جديدة",
"save_expense": "حفظ النفقات",
"update_expense": "تحديث النفقات",
"download_receipt": "تنزيل السند",
"edit_expense": "تعديل النفقات",
"new_expense": "نفقات جديدة",
"expense": "إنفاق | نفقات",
"no_expenses": "لا يوجد نفقات حتى الآن!",
"list_of_expenses": "هذه القائمة ستحتوي النفقات الخاصة بك",
"confirm_delete": "لن تتمكن من استرجاع هذا الإنفاق | لن تتمكن من استرجاع هذه النفقات",
"created_message": "تم إنشاء النفقات بنجاح",
"updated_message": "تم تحديث النفقات بنجاح",
"deleted_message": "تم حذف النفقات بنجاح",
"categories": {
"categories_list": "قائمة الفئات",
"title": "العنوان",
"name": "الاسم",
"description": "الوصف",
"amount": "المبلغ المطلوب",
"actions": "العمليات",
"add_category": "إضافة فئمة",
"new_category": "فئة جديدة",
"category": "فئة | فئات",
"select_a_category": "اختر الفئة"
}
},
"login": {
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"forgot_password": "نسيت كلمة المرور؟",
"or_signIn_with": "أو سجل الدخول بواسطة",
"login": "دخول",
"register": "تسجيل",
"reset_password": "إعادة تعيين كلمة المرور",
"password_reset_successfully": "تم إعادة تعيين كلمة المرور بنجاح",
"enter_email": "أدخل البريد الالكتروني",
"enter_password": "أكتب كلمة المرور",
"retype_password": "أعد كتابة كلمة المرور",
"login_placeholder": "mail@example.com"
},
"reports": {
"title": "تقرير",
"from_date": "من تاريخ",
"to_date": "حتى تاريخ",
"status": "الحالة",
"paid": "مدفوع",
"unpaid": "غير مدفوع",
"download_pdf": "تنزيل PDF",
"view_pdf": "عرض PDF",
"update_report": "تحديث التقرير",
"report": "تقرير | تقارير",
"profit_loss": {
"profit_loss": "الخسائر والأرباح",
"to_date": "حتى تاريخ",
"from_date": "من تاريخ",
"date_range": "اختر مدى التاريخ"
},
"sales": {
"sales": "المبيعات",
"date_range": "اختر مدى التاريخ",
"to_date": "حتى تاريخ",
"from_date": "من تاريخ",
"report_type": "نوع التقرير"
},
"taxes": {
"taxes": "الضرائب",
"to_date": "حتى تاريخ",
"from_date": "من تاريخ",
"date_range": "اختر مدى التاريخ"
},
"errors": {
"required": "حقل مطلوب"
},
"invoices": {
"invoice": "الفاتورة",
"invoice_date": "تاريخ الفاتورة",
"due_date": "تاريخ الاستحقاق",
"amount": "المبلغ المطلوب",
"contact_name": "اسم التواصل",
"status": "الحالة"
},
"estimates": {
"estimate": "تقدير",
"estimate_date": "تاريخ التقدير",
"due_date": "مستحق بتاريخ",
"estimate_number": "رقم مستحق",
"ref_number": "رقم المرجع",
"amount": "المبلغ المطلوب",
"contact_name": "اسم التواصل",
"status": "الحالة"
},
"expenses": {
"expenses": "النفقات",
"category": "الفئة",
"date": "التاريخ",
"amount": "المبلغ المطلوب",
"to_date": "حتى تاريخ",
"from_date": "من تاريخ",
"date_range": "اختر مدى التاريخ"
}
},
"settings": {
"menu_title": {
"account_settings": "إعدادات الحساب",
"company_information": "معلومات المنشأة",
"customization": "تخصيص",
"preferences": "تفضيلات",
"notifications": "تنبيهات",
"tax_types": "نوع الضريبة",
"expense_category": "فئات النفقات",
"update_app": "تحديث النظام"
},
"title": "إعدادات",
"setting": "إعدادات | إعدادات",
"general": "عام",
"language": "اللغة",
"primary_currency": "العملة الرئيسية",
"timezone": "المنطقة الزمنية",
"date_format": "صيغة التاريخ",
"currencies": {
"title": "العملات",
"currency": "العملة | العملات",
"currencies_list": "قائمة العملات",
"select_currency": "اختر العملة",
"name": "الاسم",
"code": "المرجع",
"symbol": "الرمز",
"precision": "الدقة",
"thousand_separator": "فاصل الآلاف",
"decimal_separator": "الفاصلة العشرية",
"position": "الموقع",
"position_of_symbol": "موقع رمز العملة",
"right": "يمين",
"left": "يسار",
"action": "إجراء",
"add_currency": "أضف عملة"
},
"mail": {
"host": "خادم البريد",
"port": "منفذ البريد",
"driver": "مشغل البريد",
"secret": "سري",
"mailgun_secret": "الرمز السري لـ Mailgun",
"mailgun_domain": "المجال",
"mailgun_endpoint": "النهاية الطرفية لـ Mailgun",
"ses_secret": "SES الرمز السري",
"ses_key": "SES مفتاح",
"password": "كلمة مرور البريد الالكتروني",
"username": "اسم المستخدم للبريد الإلكتروني",
"mail_config": "إعدادات البريد الالكتروني",
"from_name": "اسم المرسل",
"from_mail": "عنوان البريد الالكتروني للمرسل",
"encryption": "صيغة ا لتشفير",
"mail_config_desc": "أدناه هو نموذج لتكوين برنامج تشغيل البريد الإلكتروني لإرسال رسائل البريد الإلكتروني من التطبيق. يمكنك أيضًا تهيئة موفري الجهات الخارجية مثل Sendgrid و SES إلخ."
},
"pdf": {
"title": "PDF إعدادات",
"footer_text": "نص التذييل",
"pdf_layout": "اتجاه صفحة PDF"
},
"company_info": {
"company_info": "معلومات الشركة",
"company_name": "اسم الشركة",
"company_logo": "شعار الشركة",
"section_description": "معلومات عن شركتك سيتم عرضها على الفواتير والتقديرات والمستندات الأخرى.",
"phone": "الهاتف",
"country": "الدولة",
"state": "الولاية/المنطقة",
"city": "المدينة",
"address": "العنوان",
"zip": "الرمز البريدي",
"save": "حفظ",
"updated_message": "تم تحديث معلومات الشركة بنجاح"
},
"customization": {
"customization": "التخصيص",
"save": "حفظ",
"addresses": {
"title": "العنوان",
"section_description": "يمكنك ضبط عنوان إرسال فواتير العملاء وتنسيق عنوان شحن العملاء (معروض في PDF فقط).",
"customer_billing_address": "عنوان فواتير العميل",
"customer_shipping_address": "عنوان الشحن للعميل",
"company_address": "عنوان الشركة",
"insert_fields": "أضف حقل",
"contact": "تواصل",
"address": "العنوان",
"display_name": "الاسم الظاهر",
"primary_contact_name": "مسؤول التواصل الرئيسي",
"email": "البريد الإلكتروني",
"website": "موقع الإنترنت",
"name": "الاسم",
"country": "الدولة",
"state": "الولاية/المنطقة",
"city": "المدينة",
"company_name": "اسم الشركة",
"address_street_1": "عنوان الشارع 1",
"address_street_2": "عنوان الشارع 2",
"phone": "الهاتف",
"zip_code": "الرمز البريدي",
"address_setting_updated": "تم تحديث العنوان بنجاح"
},
"updated_message": "تم تحديث معلومات الشركة بنجاح",
"invoices": {
"title": "الفواتير",
"notes": "ملاحظات",
"invoice_prefix": "بادئة رقم الفاتورة",
"invoice_settings": "إعدادات الفاتورة",
"autogenerate_invoice_number": "ترقيم آلي للفاتورة",
"invoice_setting_description": "تعطيل الترقيم الآلي ، إذا كنت لا ترغب في إنشاء أرقام الفاتورة تلقائيًا في كل مرة تقوم فيها بإنشاء فاتورة جديدة.",
"enter_invoice_prefix": "أدخل بادئة رقم الفاتورة",
"terms_and_conditions": "الأحكام والشروط",
"invoice_setting_updated": "تم تحديث إعداد الفاتورة بنجاح"
},
"estimates": {
"title": "التقديرات",
"estimate_prefix": "بادئة رقم التقدير",
"estimate_settings": "إعدادت التقدير",
"autogenerate_estimate_number": "ترقيم آلي للتقدير",
"estimate_setting_description": "تعطيل الترقيم الآلي ، إذا كنت لا ترغب في إنشاء أرقام التقديرات تلقائيًا في كل مرة تقوم فيها بإنشاء تقدير جديد.",
"enter_estimate_prefix": "أدخل بادئة رقم التقدير",
"estimate_setting_updated": "تم تحديث إعدادات التقدير بنجاح"
},
"payments": {
"title": "المدفوعات",
"payment_prefix": "بادئة رقم الدفعة",
"payment_settings": "إعدادات الدفعة",
"autogenerate_payment_number": "ترقيم آلي للمدفوعات",
"payment_setting_description": "تعطيل الترقيم الآلي ، إذا كنت لا ترغب في إنشاء أرقام الدفعة تلقائيًا في كل مرة تقوم فيها بإنشاء دفعة جديدة.",
"enter_payment_prefix": "أدخل بادئة رقم الدفعة",
"payment_setting_updated": "تم تحديث إعدادات الدفعة بنجاح"
}
},
"account_settings": {
"profile_picture": "صورة الملف الشخصي",
"name": "الاسم",
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"confirm_password": "أعد كتابة كلمة المرور",
"account_settings": "إعدادات الجساب",
"save": "حفظ",
"section_description": "يمكنك تحديث اسمك والبريد الإلكتروني وكلمة المرور باستخدام النموذج أدناه.",
"updated_message": "تم تحديث إعدادات الحساب بنجاح"
},
"user_profile": {
"name": "الاسم",
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"confirm_password": "أعد كتابة كلمة المرور"
},
"notification": {
"title": "الإشعارات",
"email": "إرسال الإشعارات إلى",
"description": "ما هي إشعارات البريد الإلكتروني التي ترغب في تلقيها عندما يتغير شيء ما؟",
"invoice_viewed": "تم عرض الفاتورة",
"invoice_viewed_desc": "عندما يستعرض عميلك الفاتورة المرسلة عبر الشاشة الرئيسية.",
"estimate_viewed": "تم عرض التقدير",
"estimate_viewed_desc": "عندما يستعرض عميلك التقدير المرسلة عبر الشاشة الرئيسية.",
"save": "حفظ",
"email_save_message": "تم حفظ البريد الإلكتروني بنجاح",
"please_enter_email": "فضلاً أدخل البريد الإلكتروني"
},
"tax_types": {
"title": "أنواع الضرائب",
"add_tax": "أضف ضريبة",
"description": "يمكنك إضافة أو إزالة الضرائب كما يحلو لك. النظام يدعم الضرائب على العناصر الفردية وكذلك على الفاتورة.",
"add_new_tax": "إضافة ضريبة جديدة",
"tax_settings": "إعدادات الضريبة",
"tax_per_item": "ضريبة على الصنف",
"tax_name": "اسم الضريبة",
"compound_tax": "ضريبة مجمعة",
"percent": "نسبة مؤوية",
"action": "إجراء",
"tax_setting_description": "قم بتمكين هذا إذا كنت تريد إضافة ضرائب لعناصر الفاتورة الفردية. بشكل افتراضي ، تضاف الضرائب مباشرة إلى الفاتورة.",
"created_message": "تم إنشاء نوع الضريبة بنجاح",
"updated_message": "تم تحديث نوع الضريبة بنجاح",
"deleted_message": "تم حذف نوع الضريبة بنجاح",
"confirm_delete": "لن تتمكن من استرجاع نوع الضرية هذا",
"already_in_use": "ضريبة قيد الاستخدام"
},
"expense_category": {
"title": "فئات النفقات",
"action": "إجراء",
"description": "الفئات مطلوبة لإضافة إدخالات النفقات. يمكنك إضافة أو إزالة هذه الفئات وفقًا لتفضيلاتك.",
"add_new_category": "إضافة فئة جديدة",
"category_name": "اسم الفئة",
"category_description": "الوصف",
"created_message": "تم إنشاء نوع النفقات بنجاح",
"deleted_message": "تم حذف نوع النفقات بنجاح",
"updated_message": "تم تحديث نوع النفقات بنجاح",
"confirm_delete": "لن تتمكن من استرجاع نوع النفقات هذا",
"already_in_use": "نوع قيد الاستخدام"
},
"preferences": {
"currency": "العملة",
"language": "اللغة",
"time_zone": "المنطة الزمنية",
"fiscal_year": "السنة المالية",
"date_format": "صيغة التاريخ",
"discount_setting": "إعدادات الخصم",
"discount_per_item": "خصم على الصنف ",
"discount_setting_description": "قم بتمكين هذا إذا كنت تريد إضافة خصم إلى عناصر الفاتورة الفردية. بشكل افتراضي ، يتم إضافة الخصم مباشرة إلى الفاتورة.",
"save": "حفظ",
"preference": "تفضيل | تفضيلات",
"general_settings": "التفضيلات الافتراضية للنظام.",
"updated_message": "تم تحديث التفضيلات بنجاح",
"select_language": "اختر اللغة",
"select_time_zone": "اختر المنطة الزمنية",
"select_date_formate": "اختر صيغة التاريخ",
"select_financial_year": "اختر السنة المالية"
},
"update_app": {
"title": "تحديث النظام",
"description": "يمكنك تحديث النظام بسهولة عن طريق البحث عن تحديث جديد بالنقر فوق الزر أدناه",
"check_update": "تحقق من التحديثات",
"avail_update": "تحديث جديد متوفر",
"next_version": "النسخة الجديدة",
"update": "حدث الآن",
"update_progress": "قيد التحديث...",
"progress_text": "سوف يستغرق التحديث بضع دقائق. يرجى عدم تحديث الشاشة أو إغلاق النافذة قبل انتهاء التحديث",
"update_success": "تم تحديث النظام! يرجى الانتظار حتى يتم إعادة تحميل نافذة المتصفح تلقائيًا.",
"latest_message": "لا يوجد تحديثات متوفرة! لديك حالياً أحدث نسخة.",
"current_version": "النسخة الحالية"
}
},
"wizard": {
"account_info": "معلومات الحساب",
"account_info_desc": "سيتم استخدام التفاصيل أدناه لإنشاء حساب المسؤول الرئيسي. كما يمكنك تغيير التفاصيل في أي وقت بعد تسجيل الدخول.",
"name": "الاسم",
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"confirm_password": "أعد كتابة كلمة المرور",
"save_cont": "حفظ واستمرار",
"company_info": "معلومات الشركة",
"company_info_desc": "سيتم عرض هذه المعلومات على الفواتير. لاحظ أنه يمكنك تعديل هذا لاحقًا في صفحة الإعدادات.",
"company_name": "اسم الشركة",
"company_logo": "شعار الشركة",
"logo_preview": "استعراض الشعار",
"preferences": "التفضيلات",
"preferences_desc": "التفضيلات الافتراضية للنظام",
"country": "الدولة",
"state": "الولاية/المنطقة",
"city": "المدينة",
"address": "العنوان",
"street": "العنوان 1 | العنوان 2",
"phone": "الهاتف",
"zip_code": "الرمز البريدي",
"go_back": "للخلف",
"currency": "العملة",
"language": "اللغة",
"time_zone": "المنطة الزمنية",
"fiscal_year": "السنة المالية",
"date_format": "صيغة التاريخ",
"from_address": "من العنوان",
"username": "اسم المستخدم",
"next": "التالي",
"continue": "استمرار",
"skip": "تخطي",
"database": {
"database": "عنوان قاعدة البيانات",
"connection": "اتصال قاعدة البيانات",
"host": "خادم قاعدة البيانات",
"port": "منفذ قاعدة البيانات",
"password": "كلمة مرور قاعدة البيانات",
"app_url": "عنوان الإنترنت للنظام",
"username": "اسم المستخدم لقاعدة البيانات",
"db_name": "سم قاعدة البيانات",
"desc": "قم بإنشاء قاعدة بيانات على الخادم الخاص بك وتعيين بيانات الاعتماد باستخدام النموذج أدناه."
},
"permissions": {
"permissions": "الأذونات",
"permission_confirm_title": "هل أنت متأكد من الاستمرار؟",
"permission_confirm_desc": "فشل فحص أذونات المجلد",
"permission_desc": "فيما يلي قائمة أذونات المجلد المطلوبة حتى يعمل التطبيق. في حالة فشل فحص الإذن ، تأكد من تحديث أذونات المجلد."
},
"mail": {
"host": "خادم البريد",
"port": "منفذ البريد",
"driver": "مشغل البريد",
"secret": "سري",
"mailgun_secret": "الرمز السري لـ Mailgun",
"mailgun_domain": "المجال",
"mailgun_endpoint": "النهاية الطرفية لـ Mailgun",
"ses_secret": "SES الرمز السري",
"ses_key": "SES مفتاح",
"password": "كلمة مرور البريد الالكتروني",
"username": "اسم المستخدم للبريد الإلكتروني",
"mail_config": "إعدادات البريد الالكتروني",
"from_name": "اسم المرسل",
"from_mail": "عنوان البريد الالكتروني للمرسل",
"encryption": "صيغة ا لتشفير",
"mail_config_desc": "أدناه هو نموذج لتكوين برنامج تشغيل البريد الإلكتروني لإرسال رسائل البريد الإلكتروني من التطبيق. يمكنك أيضًا تهيئة موفري الجهات الخارجية مثل Sendgrid و SES إلخ."
},
"req": {
"system_req": "متطلبات النظام",
"php_req_version": "Php (النسخة المطلوبة {version} بحد أدنى)",
"check_req": "فحص متطلبات النظام",
"system_req_desc": "يحتوي النظام على بعض متطلبات الخادم. تأكد من أن خادمك لديه نسخة php المطلوبة وجميع الامتدادات المذكورة أدناه."
},
"errors": {
"migrate_failed": "فشل إنشاء الجداول",
"database_variables_save_error": "غير قادر على الاتصال بقاعدة البيانات باستخدام القيم المقدمة.",
"mail_variables_save_error": "فشل تكوين البريد الإلكتروني.",
"connection_failed": "فشل اتصال قاعدة البيانات",
"database_should_be_empty": "يجب أن تكون قاعدة البيانات فارغة"
},
"success": {
"mail_variables_save_successfully": "تم تكوين البريد الإلكتروني بنجاح",
"database_variables_save_successfully": "تم تكوين قاعدة البيانات بنجاح."
}
},
"layout_login": {
"copyright_crater": "حقوق الطبع والنشر @ كريتر - 2019",
"super_simple_invoicing": "فوترة سهلة ومبسطة للغاية",
"for_freelancer": "للمبرمجين المستقلين",
"small_businesses": "والأعال الصغيرة ",
"crater_help": "كريتر يساعدك على تتبع النفقات وتسجيل المدفوعات وإنشاء",
"invoices_and_estimates": "الفواتير والتقديرات مع إمكانية اختيار قوالب متعددة."
},
"validation": {
"invalid_url": "عنوان انترنت غير صحيح (مثال: http://www.crater.com)",
"required": "حقل مطلوب",
"email_incorrect": "بريد الكتروني غير صحيح.",
"email_already_taken": "هذا البريد الالكتروني مستخدم مسبقاً",
"email_does_not_exist": "لا يوجد كستخدم بهذا البريد الالكتروني",
"send_reset_link": "أرسال رابط استعادة كلمة المرور",
"not_yet": "ليس بعد؟ أعد الإرسال الآن..",
"password_min_length": "كلمة المرور يجب أن تتكون من {count} أحرف على الأقل",
"name_min_length": "الاسم يجب أن يتكون من {count} أحرف على الأقل",
"enter_valid_tax_rate": "أدخل معدل الضريبة بشكل صحيح",
"numbers_only": "أرقام فقط.",
"characters_only": "حروف فقط.",
"password_incorrect": "يجب أن تكون كلمات المرور متطابقة",
"password_length": "يجب أن تكون كلمة المرور بطول {count} حرف.",
"qty_must_greater_than_zero": "الكمية يجب أن تكون أكبر من صفر.",
"price_greater_than_zero": "السعر يجب أن يكون أكبر من صفر.",
"payment_greater_than_zero": "الدفعة يجب أن تكون أكبر من صفر.",
"payment_greater_than_due_amount": "مبلغ الدفعة أكثر من المبلغ المستحق لهذه الفاتورة.",
"quantity_maxlength": "يجب ألا تزيد الكمية عن 20 رقماً.",
"price_maxlength": "يجب ألا يزيد السعر عن 20 رقماً.",
"price_minvalue": "يجب أن يكون السعر أكبر من صفر.",
"amount_maxlength": "يجب ألا يزيد المبلغ عن 20 رقماً.",
"amount_minvalue": "يجب أن يكون المبلغ أكبر من صفر.",
"description_maxlength": "يجب ألا يزيد الوصف عن 255 حرفاً.",
"maximum_options_error": "الحد الأعلى هو {max} خيارات. قم بإزالة أحد الخيارات لتحديد خيار آخر.",
"notes_maxlength": "يجب ألا يزيد حجم الملاحظات عن 255 حرفاً.",
"address_maxlength": "يجب ألا يزيد العنوان عن 255 حرفاً.",
"ref_number_maxlength": "يجب ألا يزيد الرقم المرجعي عن 255 حرفاً.",
"prefix_maxlength": "يجب ألا تزيد البادئة عن 5 أحرف."
}
}

View File

@ -530,6 +530,7 @@
"menu_title": {
"account_settings": "Account Settings",
"company_information": "Company Information",
"customization": "Customization",
"preferences": "Preferences",
"notifications": "Notifications",
"tax_types": "Tax Types",
@ -598,6 +599,67 @@
"save": "Save",
"updated_message": "Company information updated successfully"
},
"customization": {
"customization": "customization",
"save": "Save",
"addresses": {
"title": "Addresses",
"section_description": "You can set Customer Billing Address and Customer Shipping Address Format (Displayed in PDF only). ",
"customer_billing_address": "Customer Billing Address",
"customer_shipping_address": "Customer Shipping Address",
"company_address": "Company Address",
"insert_fields": "Insert Fields",
"contact": "Contact",
"address": "Address",
"display_name": "Display Name",
"primary_contact_name": "Primary Contact Name",
"email": "Email",
"website": "Website",
"name": "Name",
"country": "Country",
"state": "State",
"city": "City",
"company_name": "Company Name",
"address_street_1": "Address Street 1",
"address_street_2": "Address Street 2",
"phone": "Phone",
"zip_code": "Zip Code",
"address_setting_updated": "Address Setting updated successfully"
},
"updated_message": "Company information updated successfully",
"invoices": {
"title": "Invoices",
"notes": "Notes",
"invoice_prefix": "Invoice Prefix",
"invoice_settings": "Invoice Settings",
"autogenerate_invoice_number": "Autogenerate Invoice Number",
"invoice_setting_description": "Disable this, If you don't wish to auto-generate invoice numbers each time you create a new invoice.",
"enter_invoice_prefix": "Enter invoice prefix",
"terms_and_conditions": "Terms and Conditions",
"invoice_setting_updated": "Invoice Setting updated successfully"
},
"estimates": {
"title": "Estimates",
"estimate_prefix": "Estimate Prefix",
"estimate_settings": "Estimate Settings",
"autogenerate_estimate_number": "Autogenerate Estimate Number",
"estimate_setting_description": "Disable this, If you don't wish to auto-generate estimate numbers each time you create a new estimate.",
"enter_estimate_prefix": "Enter estmiate prefix",
"estimate_setting_updated": "Estimate Setting updated successfully"
},
"payments": {
"title": "Payments",
"payment_prefix": "Payment Prefix",
"payment_settings": "Payment Settings",
"autogenerate_payment_number": "Autogenerate Payment Number",
"payment_setting_description": "Disable this, If you don't wish to auto-generate payment numbers each time you create a new payment.",
"enter_payment_prefix": "Enter Payment Prefix",
"payment_setting_updated": "Payment Setting updated successfully"
}
},
"account_settings": {
"profile_picture": "Profile Picture",
"name": "Name",
@ -666,7 +728,7 @@
"date_format": "Date Format",
"discount_setting": "Discount Setting",
"discount_per_item": "Discount Per Item ",
"discount_setting_description": "Enable this if you want to add Discount to individual invoice items. By default, Discount are added directly to the invoice.",
"discount_setting_description": "Enable this if you want to add Discount to individual invoice items. By default, Discount is added directly to the invoice.",
"save": "Save",
"preference": "Preference | Preferences",
"general_settings": "Default preferences for the system.",
@ -812,6 +874,7 @@
"maximum_options_error": "Maximum of {max} options selected. First remove a selected option to select another.",
"notes_maxlength": "Notes should not be greater than 255 characters.",
"address_maxlength": "Address should not be greater than 255 characters.",
"ref_number_maxlength": "Ref Number should not be greater than 255 characters."
"ref_number_maxlength": "Ref Number should not be greater than 255 characters.",
"prefix_maxlength": "Prefix should not be greater than 5 characters."
}
}

View File

@ -3,6 +3,7 @@ import VueI18n from 'vue-i18n'
import en from './en.json'
import fr from './fr.json'
import es from './es.json'
import ar from './ar.json'
Vue.use(VueI18n)
@ -11,7 +12,8 @@ const i18n = new VueI18n({
messages: {
en,
fr,
es
es,
ar
}
})

View File

@ -66,6 +66,7 @@ import ReportLayout from './views/reports/layout/Index.vue'
// Settings
import SettingsLayout from './views/settings/layout/Index.vue'
import CompanyInfo from './views/settings/CompanyInfo.vue'
import Customization from './views/settings/Customization.vue'
import Notifications from './views/settings/Notifications.vue'
import Preferences from './views/settings/Preferences.vue'
import UserProfile from './views/settings/UserProfile.vue'
@ -309,6 +310,11 @@ const routes = [
name: 'company.info',
component: CompanyInfo
},
{
path: 'customization',
name: 'customization',
component: Customization
},
{
path: 'user-profile',
name: 'user.profile',

View File

@ -162,7 +162,7 @@
:options="billingCountries"
:searchable="true"
:show-labels="false"
:allow-empty="false"
:allow-empty="true"
:tabindex="8"
:placeholder="$t('general.select_country')"
label="name"
@ -265,7 +265,7 @@
:searchable="true"
:show-labels="false"
:tabindex="16"
:allow-empty="false"
:allow-empty="true"
:placeholder="$t('general.select_country')"
label="name"
track-by="id"
@ -411,6 +411,36 @@ export default {
return true
}
return false
},
hasBillingAdd () {
let billing = this.billing
if (
billing.name ||
billing.country_id ||
billing.state ||
billing.city ||
billing.phone ||
billing.zip ||
billing.address_street_1 ||
billing.address_street_2) {
return true
}
return false
},
hasShippingAdd () {
let shipping = this.shipping
if (
shipping.name ||
shipping.country_id ||
shipping.state ||
shipping.city ||
shipping.phone ||
shipping.zip ||
shipping.address_street_1 ||
shipping.address_street_2) {
return true
}
return false
}
},
watch: {
@ -418,12 +448,16 @@ export default {
if (newCountry) {
this.billing.country_id = newCountry.id
this.isDisabledBillingState = false
} else {
this.billing.country_id = null
}
},
shipping_country (newCountry) {
if (newCountry) {
this.shipping.country_id = newCountry.id
return true
} else {
this.shipping.country_id = null
}
}
},
@ -446,7 +480,14 @@ export default {
]),
async loadCustomer () {
let { data: { customer, currencies, currency } } = await this.fetchCustomer(this.$route.params.id)
this.formData = customer
this.formData.id = customer.id
this.formData.name = customer.name
this.formData.contact_name = customer.contact_name
this.formData.email = customer.email
this.formData.phone = customer.phone
this.formData.currency_id = customer.currency_id
this.formData.website = customer.website
if (customer.billing_address) {
this.billing = customer.billing_address
@ -495,7 +536,16 @@ export default {
if (this.$v.$invalid) {
return true
}
this.formData.addresses = [{...this.billing}, {...this.shipping}]
if (this.hasBillingAdd && this.hasShippingAdd) {
this.formData.addresses = [{...this.billing}, {...this.shipping}]
} else {
if (this.hasBillingAdd) {
this.formData.addresses = [{...this.billing}]
}
if (this.hasShippingAdd) {
this.formData.addresses = [{...this.shipping}]
}
}
if (this.isEdit) {
if (this.currency) {

View File

@ -32,8 +32,8 @@
class="show-customer"
>
<div class="row px-2 mt-1">
<div class="col col-6">
<div v-if="selectedCustomer.billing_address != null" class="row address-menu">
<div v-if="selectedCustomer.billing_address" class="col col-6">
<div class="row address-menu">
<label class="col-sm-4 px-2 title">{{ $t('general.bill_to') }}</label>
<div class="col-sm p-0 px-2 content">
<label v-if="selectedCustomer.billing_address.name">
@ -57,8 +57,8 @@
</div>
</div>
</div>
<div class="col col-6">
<div v-if="selectedCustomer.shipping_address != null" class="row address-menu">
<div v-if="selectedCustomer.shipping_address" class="col col-6">
<div class="row address-menu">
<label class="col-sm-4 px-2 title">{{ $t('general.ship_to') }}</label>
<div class="col-sm p-0 px-2 content">
<label v-if="selectedCustomer.shipping_address.name">
@ -84,7 +84,7 @@
</div>
</div>
<div class="customer-content mb-1">
<label class="email">{{ selectedCustomer.email ? selectedCustomer.email : selectedCustomer.name }}</label>
<label class="email">{{ selectedCustomer.name }}</label>
<label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label>
</div>
</div>
@ -127,14 +127,15 @@
<div class="row mt-4">
<div class="col collapse-input">
<label>{{ $t('estimates.estimate_number') }}<span class="text-danger"> * </span></label>
<base-input
:invalid="$v.newEstimate.estimate_number.$error"
:read-only="true"
v-model="newEstimate.estimate_number"
<base-prefix-input
v-model="estimateNumAttribute"
:invalid="$v.estimateNumAttribute.$error"
:prefix="estimatePrefix"
icon="hashtag"
@input="$v.newEstimate.estimate_number.$touch()"
@input="$v.estimateNumAttribute.$touch()"
/>
<span v-show="$v.newEstimate.estimate_number.$error && !$v.newEstimate.estimate_number.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span>
<span v-show="$v.estimateNumAttribute.$error && !$v.estimateNumAttribute.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span>
<span v-show="!$v.estimateNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div>
<div class="col collapse-input">
<label>{{ $t('estimates.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid'
import TaxStub from '../../stub/tax'
import Tax from './EstimateTax'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null,
initLoading: false,
isLoading: false,
maxDiscount: 0
maxDiscount: 0,
estimatePrefix: null,
estimateNumAttribute: null
}
},
validations () {
@ -373,9 +376,6 @@ export default {
expiry_date: {
required
},
estimate_number: {
required
},
discount_val: {
between: between(0, this.subtotal)
},
@ -388,6 +388,10 @@ export default {
},
selectedCustomer: {
required
},
estimateNumAttribute: {
required,
numeric
}
}
},
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency
this.estimateTemplates = response.data.estimateTemplates
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumber
}
this.initLoading = false
return
@ -574,8 +580,9 @@ export default {
let today = new Date()
this.newEstimate.estimate_date = moment(today).toString()
this.newEstimate.expiry_date = moment(today).add(7, 'days').toString()
this.newEstimate.estimate_number = response.data.nextEstimateNumber
this.itemList = response.data.items
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumberAttribute
}
this.initLoading = false
},
@ -604,6 +611,7 @@ export default {
}
this.isLoading = true
this.newEstimate.estimate_number = this.estimatePrefix + '-' + this.estimateNumAttribute
let data = {
...this.newEstimate,
@ -637,7 +645,11 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
console.log(err)
if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
})
},
submitUpdate (data) {
@ -650,7 +662,11 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
console.log(err)
if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
})
},
checkItemsData (index, isValid) {

View File

@ -30,8 +30,8 @@
<div
v-if="selectedCustomer" class="show-customer">
<div class="row px-2 mt-1">
<div class="col col-6">
<div v-if="selectedCustomer.billing_address" class="row address-menu">
<div v-if="selectedCustomer.billing_address" class="col col-6">
<div class="row address-menu">
<label class="col-sm-4 px-2 title">{{ $t('general.bill_to') }}</label>
<div class="col-sm p-0 px-2 content">
<label v-if="selectedCustomer.billing_address.name">
@ -55,8 +55,8 @@
</div>
</div>
</div>
<div class="col col-6">
<div v-if="selectedCustomer.shipping_address" class="row address-menu">
<div v-if="selectedCustomer.shipping_address" class="col col-6">
<div class="row address-menu">
<label class="col-sm-4 px-2 title">{{ $t('general.ship_to') }}</label>
<div class="col-sm p-0 px-2 content">
<label v-if="selectedCustomer.shipping_address.name">
@ -127,14 +127,15 @@
<div class="row mt-4">
<div class="col collapse-input">
<label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label>
<base-input
:invalid="$v.newInvoice.invoice_number.$error"
:read-only="true"
v-model="newInvoice.invoice_number"
<base-prefix-input
v-model="invoiceNumAttribute"
:invalid="$v.invoiceNumAttribute.$error"
:prefix="invoicePrefix"
icon="hashtag"
@input="$v.newInvoice.invoice_number.$touch()"
@input="$v.invoiceNumAttribute.$touch()"
/>
<span v-show="$v.newInvoice.invoice_number.$error && !$v.newInvoice.invoice_number.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span>
<span v-show="$v.invoiceNumAttribute.$error && !$v.invoiceNumAttribute.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span>
<span v-show="!$v.invoiceNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div>
<div class="col collapse-input">
<label>{{ $t('invoices.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid'
import TaxStub from '../../stub/tax'
import Tax from './InvoiceTax'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null,
initLoading: false,
isLoading: false,
maxDiscount: 0
maxDiscount: 0,
invoicePrefix: null,
invoiceNumAttribute: null
}
},
validations () {
@ -373,9 +376,6 @@ export default {
due_date: {
required
},
invoice_number: {
required
},
discount_val: {
between: between(0, this.subtotal)
},
@ -388,6 +388,10 @@ export default {
},
selectedCustomer: {
required
},
invoiceNumAttribute: {
required,
numeric
}
}
},
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency
this.invoiceTemplates = response.data.invoiceTemplates
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumber
}
this.initLoading = false
return
@ -574,8 +580,9 @@ export default {
let today = new Date()
this.newInvoice.invoice_date = moment(today).toString()
this.newInvoice.due_date = moment(today).add(7, 'days').toString()
this.newInvoice.invoice_number = response.data.nextInvoiceNumber
this.itemList = response.data.items
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumberAttribute
}
this.initLoading = false
},
@ -604,6 +611,7 @@ export default {
}
this.isLoading = true
this.newInvoice.invoice_number = this.invoicePrefix + '-' + this.invoiceNumAttribute
let data = {
...this.newInvoice,
@ -637,6 +645,10 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err)
})
},
@ -653,6 +665,10 @@ export default {
}
}).catch((err) => {
this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err)
})
},

View File

@ -50,7 +50,7 @@ export default {
Layout.set('layout-default')
},
created() {
created () {
this.bootstrap().then((res) => {
this.setInitialCompany()
})
@ -59,7 +59,7 @@ export default {
methods: {
...mapActions(['bootstrap']),
...mapActions('company', ['setSelectedCompany']),
setInitialCompany() {
setInitialCompany () {
let selectedCompany = Ls.get('selectedCompany') !== null
if (selectedCompany) {

View File

@ -40,16 +40,15 @@
<div class="col-sm-6">
<div class="form-group">
<label class="form-label">{{ $t('payments.payment_number') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.formData.payment_number.$error"
v-model.trim="formData.payment_number"
read-only
type="text"
name="email"
@input="$v.formData.payment_number.$touch()"
<base-prefix-input
:invalid="$v.paymentNumAttribute.$error"
v-model.trim="paymentNumAttribute"
:prefix="paymentPrefix"
@input="$v.paymentNumAttribute.$touch()"
/>
<div v-if="$v.formData.payment_number.$error">
<span v-if="!$v.formData.payment_number.required" class="text-danger">{{ $tc('validation.required') }}</span>
<div v-if="$v.paymentNumAttribute.$error">
<span v-if="!$v.paymentNumAttribute.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.paymentNumAttribute.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
</div>
</div>
</div>
@ -155,7 +154,7 @@ import { mapActions, mapGetters } from 'vuex'
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import moment from 'moment'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: { MultiSelect },
@ -184,7 +183,9 @@ export default {
invoiceList: [],
isLoading: false,
maxPayableAmount: Number.MAX_SAFE_INTEGER,
isSettingInitialData: true
isSettingInitialData: true,
paymentNumAttribute: null,
paymentPrefix: ''
}
},
validations () {
@ -193,9 +194,6 @@ export default {
required
},
formData: {
payment_number: {
required
},
payment_date: {
required
},
@ -206,6 +204,10 @@ export default {
notes: {
maxLength: maxLength(255)
}
},
paymentNumAttribute: {
required,
numeric
}
}
},
@ -297,6 +299,8 @@ export default {
this.customer = response.data.payment.user
this.formData.payment_date = moment(response.data.payment.payment_date, 'YYYY-MM-DD').toString()
this.formData.amount = parseFloat(response.data.payment.amount)
this.paymentPrefix = response.data.payment_prefix
this.paymentNumAttribute = response.data.nextPaymentNumber
if (response.data.payment.invoice !== null) {
this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount)
this.invoice = response.data.payment.invoice
@ -305,7 +309,8 @@ export default {
} else {
let response = await this.fetchCreatePayment()
this.customerList = response.data.customers
this.formData.payment_number = response.data.nextPaymentNumber
this.paymentNumAttribute = response.data.nextPaymentNumberAttribute
this.paymentPrefix = response.data.payment_prefix
this.formData.payment_date = moment(new Date()).toString()
}
return true
@ -332,6 +337,9 @@ export default {
if (this.$v.$invalid) {
return true
}
this.formData.payment_number = this.paymentPrefix + '-' + this.paymentNumAttribute
if (this.isEdit) {
let data = {
editData: {
@ -340,35 +348,53 @@ export default {
},
id: this.$route.params.id
}
let response = await this.updatePayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.updated_message'))
this.$router.push('/admin/payments')
return true
try {
let response = await this.updatePayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.updated_message'))
this.$router.push('/admin/payments')
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} else {
let data = {
...this.formData,
payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY')
}
this.isLoading = true
let response = await this.addPayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.created_message'))
this.$router.push('/admin/payments')
this.isLoading = true
return true
try {
let response = await this.addPayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.created_message'))
this.$router.push('/admin/payments')
this.isLoading = true
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
}
}
}

View File

@ -0,0 +1,385 @@
<template>
<div class="setting-main-container customization">
<div class="card setting-card">
<ul class="tabs">
<li class="tab" @click="setActiveTab('INVOICES')">
<a :class="['tab-link', {'a-active': activeTab === 'INVOICES'}]" href="#">{{ $t('settings.customization.invoices.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('ESTIMATES')">
<a :class="['tab-link', {'a-active': activeTab === 'ESTIMATES'}]" href="#">{{ $t('settings.customization.estimates.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('PAYMENTS')">
<a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.title') }}</a>
</li>
</ul>
<!-- Invoices Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'INVOICES'" class="invoice-tab">
<form action="" class="form-section" @submit.prevent="updateInvoiceSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label>
<base-input
v-model="invoices.invoice_prefix"
:invalid="$v.invoices.invoice_prefix.$error"
class="prefix-input"
@input="$v.invoices.invoice_prefix.$touch()"
@keyup="changeToUppercase('INVOICES')"
/>
<span v-show="!$v.invoices.invoice_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.invoices.invoice_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.invoices.invoice_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
<hr>
</form>
<div class="col-md-12 mt-3">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.invoices.invoice_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="invoiceAutogenerate"
class="btn-switch"
@change="setInvoiceSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.invoices.autogenerate_invoice_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.invoices.invoice_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
<!-- Estimates Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'ESTIMATES'" class="estimate-tab">
<form action="" class="form-section" @submit.prevent="updateEstimateSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label>
<base-input
v-model="estimates.estimate_prefix"
:invalid="$v.estimates.estimate_prefix.$error"
class="prefix-input"
@input="$v.estimates.estimate_prefix.$touch()"
@keyup="changeToUppercase('ESTIMATES')"
/>
<span v-show="!$v.estimates.estimate_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.estimates.estimate_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.estimates.estimate_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
<hr>
</form>
<div class="col-md-12 mt-3">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.estimates.estimate_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="estimateAutogenerate"
class="btn-switch"
@change="setEstimateSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.estimates.autogenerate_estimate_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.estimates.estimate_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
<!-- Payments Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'PAYMENTS'" class="payment-tab">
<form action="" class="form-section" @submit.prevent="updatePaymentSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label>
<base-input
v-model="payments.payment_prefix"
:invalid="$v.payments.payment_prefix.$error"
class="prefix-input"
@input="$v.payments.payment_prefix.$touch()"
@keyup="changeToUppercase('PAYMENTS')"
/>
<span v-show="!$v.payments.payment_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.payments.payment_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.payments.payment_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
</form>
<hr>
<div class="col-md-12 mt-4">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.payments.payment_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="paymentAutogenerate"
class="btn-switch"
@change="setPaymentSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.payments.autogenerate_payment_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.payments.payment_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { validationMixin } from 'vuelidate'
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
activeTab: 'INVOICES',
invoiceAutogenerate: false,
estimateAutogenerate: false,
paymentAutogenerate: false,
invoices: {
invoice_prefix: null,
invoice_notes: null,
invoice_terms_and_conditions: null
},
estimates: {
estimate_prefix: null,
estimate_notes: null,
estimate_terms_and_conditions: null
},
payments: {
payment_prefix: null
},
currentData: null
}
},
watch: {
activeTab () {
this.loadData()
}
},
validations: {
invoices: {
invoice_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
estimates: {
estimate_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
payments: {
payment_prefix: {
required,
maxLength: maxLength(5),
alpha
}
}
},
created () {
this.loadData()
},
methods: {
async setInvoiceSetting () {
let data = {
key: 'invoice_auto_generate',
value: this.invoiceAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async setEstimateSetting () {
let data = {
key: 'estimate_auto_generate',
value: this.estimateAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
changeToUppercase (currentTab) {
if (currentTab === 'INVOICES') {
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()
return true
}
if (currentTab === 'ESTIMATES') {
this.estimates.estimate_prefix = this.estimates.estimate_prefix.toUpperCase()
return true
}
if (currentTab === 'PAYMENTS') {
this.payments.payment_prefix = this.payments.payment_prefix.toUpperCase()
return true
}
},
async setPaymentSetting () {
let data = {
key: 'payment_auto_generate',
value: this.paymentAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async loadData () {
let res = await window.axios.get('/api/settings/get-customize-setting')
if (res.data) {
this.invoices.invoice_prefix = res.data.invoice_prefix
this.invoices.invoice_notes = res.data.invoice_notes
this.invoices.invoice_terms_and_conditions = res.data.invoice_terms_and_conditions
this.estimates.estimate_prefix = res.data.estimate_prefix
this.estimates.estimate_notes = res.data.estimate_notes
this.estimates.estimate_terms_and_conditions = res.data.estimate_terms_and_conditions
this.payments.payment_prefix = res.data.payment_prefix
if (res.data.invoice_auto_generate === 'YES') {
this.invoiceAutogenerate = true
} else {
this.invoiceAutogenerate = false
}
if (res.data.estimate_auto_generate === 'YES') {
this.estimateAutogenerate = true
} else {
this.estimateAutogenerate = false
}
if (res.data.payment_auto_generate === 'YES') {
this.paymentAutogenerate = true
} else {
this.paymentAutogenerate = false
}
}
},
async updateInvoiceSetting () {
this.$v.invoices.$touch()
if (this.$v.invoices.$invalid) {
return false
}
let data = {type: 'INVOICES', ...this.invoices}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.invoices.invoice_setting_updated'))
}
},
async updateEstimateSetting () {
this.$v.estimates.$touch()
if (this.$v.estimates.$invalid) {
return false
}
let data = {type: 'ESTIMATES', ...this.estimates}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.estimates.estimate_setting_updated'))
}
},
async updatePaymentSetting () {
this.$v.payments.$touch()
if (this.$v.payments.$invalid) {
return false
}
let data = {type: 'PAYMENTS', ...this.payments}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.payments.payment_setting_updated'))
}
},
async updateSetting (data) {
let res = await window.axios.put('/api/settings/update-customize-setting', data)
if (res.data.success) {
return true
}
return false
},
setActiveTab (val) {
this.activeTab = val
}
}
}
</script>
<style>
.fade-customize-enter-active {
transition: opacity 0.9s;
}
.fade-customize-leave-active {
transition: opacity 0s;
}
.fade-customize-enter, .fade-customize-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@ -45,6 +45,12 @@ export default {
icon: 'building',
iconType: 'far'
},
{
link: '/admin/settings/customization',
title: 'settings.menu_title.customization',
icon: 'edit',
iconType: 'fa'
},
{
link: '/admin/settings/preferences',
title: 'settings.menu_title.preferences',

View File

@ -203,8 +203,10 @@ export default {
this.$emit('next')
window.toastr['success'](this.$t('wizard.success.' + response.data.success))
return true
} else {
} else if (response.data.error) {
window.toastr['error'](this.$t('wizard.errors.' + response.data.error))
} else if (response.data.error_message) {
window.toastr['error'](response.data.error_message)
}
} catch (e) {
window.toastr['error'](e.response.data.message)

View File

@ -54,7 +54,7 @@
/>
<div v-if="$v.profileData.email.$error">
<span v-if="!$v.profileData.email.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.profileData.email.email" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.profileData.email.email" class="text-danger">{{ $tc('validation.email_incorrect') }}</span>
</div>
</div>
</div>
@ -145,7 +145,7 @@ export default {
},
password: {
required,
minLength: minLength(5)
minLength: minLength(8)
},
confirm_password: {
required: requiredIf('isRequired'),

View File

@ -0,0 +1,54 @@
.base-prefix-input {
display: flex;
position: relative;
width: 100%;
height: 40px;
padding: 2px 2px;
flex-direction: row;
background: #FFFFFF;
border: 1px solid $ls-color-gray--light;
border-radius: 5px;
.icon {
width: 13px;
height: 18px;
color: $ls-color-gray;
font-style: normal;
font-weight: 900;
font-size: 14px;
line-height: 16px;
margin-top: 17px;
margin-left: 20px;
z-index: 1;
transform: translate(-50%,-50%);
}
p {
padding: 0 0 0 0;
margin: 0 0 0 0;
}
.prefix-label {
display: flex;
height: 18px;
color: #55547A;
font-weight: 500;
font-size: 14px;
line-height: 16px;
padding: 9px 2px 9px 10px;
}
.prefix-input-field {
width: 100%;
padding: 8px 13px;
padding-left: 1px;
text-align: left;
background: #FFFFFF;
border: none;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 21px;
}
}

View File

@ -48,6 +48,7 @@
@import 'components/base/base-text-area';
@import "components/base/base-switch";
@import 'components/base/base-loader/index';
@import 'components/base/base-prefix-input';
// Components
@ -91,6 +92,7 @@
@import 'pages/login';
@import 'pages/login-3';
@import 'pages/404';
@import 'pages/customization';
@import 'pages/settings';
@import 'pages/invoices/create';
@import 'pages/invoices/view';

View File

@ -0,0 +1,39 @@
.customization {
.prefix-input {
max-width: 30%;
}
.form-section {
padding: 8px 15px;
}
.invoice-customization-card {
border: 1px solid #EBF1FA;border-radius: 5px;
}
@media (max-width: $x-small-breakpoint) {
.address-customization-card {
.address-fields-container {
display: flex;
flex-wrap: wrap;
.fields-list {
border-right: 0px;
}
}
}
.tabs {
.tab {
padding: 10px 10px;
}
}
}
}

View File

@ -3,9 +3,11 @@
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -58,13 +60,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -339,7 +339,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -352,7 +351,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -410,7 +408,7 @@
<div class="bill-address-container">
@include('app.pdf.estimate.partials.billing-address')
</div>
@if($estimate->user->billingaddress && ($estimate->user->billingaddress->name || $estimate->user->billingaddress->address_street_1 || $estimate->user->billingaddress->address_street_2 || $estimate->user->billingaddress->country || $estimate->user->billingaddress->state || $estimate->user->billingaddress->city || $estimate->user->billingaddress->zip || $estimate->user->billingaddress->phone))
@if($estimate->user->billingaddress)
<div class="ship-address-container">
@else
<div class="ship-address-container " style="float:left;padding-left:0px;">

View File

@ -3,9 +3,11 @@
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -62,13 +64,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -364,7 +364,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -377,7 +376,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -421,7 +419,7 @@
<div class="ship-address-container">
@include('app.pdf.estimate.partials.shipping-address')
</div>
@if($estimate->user->shippingaddress && ($estimate->user->shippingaddress->name || $estimate->user->shippingaddress->address_street_1 || $estimate->user->shippingaddress->address_street_2 || $estimate->user->shippingaddress->country || $estimate->user->shippingaddress->state || $estimate->user->shippingaddress->city || $estimate->user->shippingaddress->zip || $estimate->user->phone))
@if($estimate->user->shippingaddress)
<div class="bill-address-container">
@else
<div class="bill-address-container" style="float:right;padding-right:0px;">

View File

@ -3,9 +3,11 @@
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -64,13 +66,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -372,7 +372,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -385,7 +384,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -426,7 +424,7 @@
<div style="float:left;">
@include('app.pdf.estimate.partials.billing-address')
</div>
@if($estimate->user->billingaddress && ($estimate->user->billingaddress->name || $estimate->user->billingaddress->address_street_1 || $estimate->user->billingaddress->address_street_2 || $estimate->user->billingaddress->country || $estimate->user->billingaddress->state || $estimate->user->billingaddress->city || $estimate->user->billingaddress->zip || $estimate->user->billingaddress->phone))
@if($estimate->user->billingaddress)
<div style="float:right;">
@else
<div style="float:left;">

View File

@ -1,7 +1,5 @@
@if($estimate->user->billingaddress)
@if($estimate->user->billingaddress->name || $estimate->user->billingaddress->address_street_1 || $estimate->user->billingaddress->address_street_2 || $estimate->user->billingaddress->country || $estimate->user->billingaddress->state || $estimate->user->billingaddress->city || $estimate->user->billingaddress->zip || $estimate->user->billingaddress->phone)
<p class="bill-to">Bill To,</p>
@endif
<p class="bill-to">Bill To,</p>
@if($estimate->user->billingaddress->name)
<p class="bill-user-name">
{{$estimate->user->billingaddress->name}}
@ -16,11 +14,11 @@
{{$estimate->user->billingaddress->address_street_2}}<br>
@endif
@if($estimate->user->billingaddress->city && $estimate->user->billingaddress->city)
@if($estimate->user->billingaddress->city)
{{$estimate->user->billingaddress->city}},
@endif
@if($estimate->user->billingaddress->state && $estimate->user->billingaddress->state)
@if($estimate->user->billingaddress->state)
{{$estimate->user->billingaddress->state}}.
@endif

View File

@ -1,7 +1,5 @@
@if($estimate->user->shippingaddress)
@if($estimate->user->shippingaddress->name || $estimate->user->shippingaddress->address_street_1 || $estimate->user->shippingaddress->address_street_2 || $estimate->user->shippingaddress->country || $estimate->user->shippingaddress->state || $estimate->user->shippingaddress->city || $estimate->user->shippingaddress->zip || $estimate->user->phone)
<p class="ship-to">Ship To,</p>
@endif
<p class="ship-to">Ship To,</p>
@if($estimate->user->shippingaddress->name)
<p class="ship-user-name">
{{$estimate->user->shippingaddress->name}}

View File

@ -18,24 +18,48 @@
@endphp
@foreach ($estimate->items as $item)
<tr class="item-details">
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 20px; vertical-align: top;">{{$index}}</td>
<td class="inv-item items" style="text-align: left; color: #040405;padding-left: 0px">
<span>{{ $item->name }}</span><br>
<span style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;">{{ $item->description }}</span>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 20px; vertical-align: top;"
>
{{$index}}
</td>
<td
class="inv-item items"
style="text-align: left; color: #040405;padding-left: 0px"
>
<span>{{ $item->name }}</span><br>
<span
style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;"
>
{{ $item->description }}
</span>
</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 20px"
>
{{$item->quantity}}
</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 40px"
>
{!! format_money_pdf($item->price, $estimate->user->currency) !!}
</td>
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 20px">{{$item->quantity}}</td>
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 40px">{{$item->price/100}}</td>
@if($estimate->discount_per_item === 'YES')
<td class="inv-item items" style="text-align: right; color: #040405; padding-left: 10px">
@if($item->discount_type === 'fixed')
{{$item->discount_val/100}}
{!! format_money_pdf($item->discount_val, $estimate->user->currency) !!}
@endif
@if($item->discount_type === 'percentage')
{{$item->discount}}%
@endif
</td>
@endif
<td class="inv-item items" style="text-align: right; color: #040405;">{{$item->total/100}}</td>
<td class="inv-item items" style="text-align: right; color: #040405;">
{!! format_money_pdf($item->total, $estimate->user->currency) !!}
</td>
</tr>
@php
$index += 1
@ -47,7 +71,7 @@
<tr>
<td class="no-borde" style="color: #55547A; padding-left:10px; font-size:12px;">Subtotal</td>
<td class="no-border items"
style="padding-right:10px; text-align: right; font-size:12px; color: #040405; font-weight: 500;">{!! format_money_pdf($estimate->sub_total) !!}</td>
style="padding-right:10px; text-align: right; font-size:12px; color: #040405; font-weight: 500;">{!! format_money_pdf($estimate->sub_total, $estimate->user->currency) !!}</td>
</tr>
@if ($estimate->tax_per_item === 'YES')
@ -57,7 +81,7 @@
{{$labels[$i]}}
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
{!! format_money_pdf($taxes[$i]) !!}
{!! format_money_pdf($taxes[$i], $estimate->user->currency) !!}
</td>
</tr>
@endfor
@ -68,7 +92,7 @@
{{$tax->name.' ('.$tax->percent.'%)'}}
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
{!! format_money_pdf($tax->amount) !!}
{!! format_money_pdf($tax->amount, $estimate->user->currency) !!}
</td>
</tr>
@endforeach
@ -77,14 +101,19 @@
@if ($estimate->discount_per_item === 'NO')
<tr>
<td class="no-border" style="padding-left:10px; text-align:left; font-size:12px; color: #55547A;">
Discount ({{$estimate->discount}}%)
@if($estimate->discount_type === 'fixed')
Discount
@endif
@if($estimate->discount_type === 'percentage')
Discount ({{$estimate->discount}}%)
@endif
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
@if($estimate->discount_type === 'fixed')
{!! format_money_pdf($estimate->discount_val) !!}
{!! format_money_pdf($estimate->discount_val, $estimate->user->currency) !!}
@endif
@if($estimate->discount_type === 'percentage')
{!! format_money_pdf($estimate->discount_val) !!}
{!! format_money_pdf($estimate->discount_val, $estimate->user->currency) !!}
@endif
</td>
</tr>
@ -103,7 +132,7 @@
class="no-border total-border-right items padd8"
style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; padding-top:20px; color: #5851DB"
>
{!! format_money_pdf($estimate->total)!!}
{!! format_money_pdf($estimate->total, $estimate->user->currency)!!}
</td>
</tr>
</table>

View File

@ -3,9 +3,11 @@
<head>
<title>Invoice</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -59,13 +61,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -346,7 +346,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -359,7 +358,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -416,9 +414,8 @@
<div class="bill-add">
<div class="bill-address-container">
@include('app.pdf.invoice.partials.billing-address')
</div>
@if($invoice->user->billingaddress->name || $invoice->user->billingaddress->address_street_1 || $invoice->user->billingaddress->address_street_2 || $invoice->user->billingaddress->country || $invoice->user->billingaddress->state || $invoice->user->billingaddress->city || $invoice->user->billingaddress->zip || $invoice->user->billingaddress->phone)
@if($invoice->user->billingaddress)
<div class="ship-address-container">
@else
<div class="ship-address-container " style="float:left;padding-left:0px;">

View File

@ -3,9 +3,10 @@
<head>
<title>Invoice</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -61,13 +62,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -373,7 +372,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -386,7 +384,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -431,7 +428,7 @@
<div class="ship-address-container">
@include('app.pdf.invoice.partials.shipping-address')
</div>
@if($invoice->user->shippingaddress->name || $invoice->user->shippingaddress->address_street_1 || $invoice->user->shippingaddress->address_street_2 || $invoice->user->shippingaddress->country || $invoice->user->shippingaddress->state || $invoice->user->shippingaddress->city || $invoice->user->shippingaddress->zip || $invoice->user->phone)
@if($invoice->user->shippingaddress)
<div class="bill-address-container">
@else
<div class="bill-address-container" style="float:right;padding-right:0px;">

View File

@ -3,9 +3,11 @@
<head>
<title>Invoice</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -64,13 +66,11 @@
margin-left:160px;
}
.header {
font-family: 'Roboto', sans-serif;
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@ -382,7 +382,6 @@
}
.notes {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 300;
font-size: 12px;
@ -395,7 +394,6 @@
}
.notes-label {
font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 15px;
@ -436,7 +434,7 @@
<div style="float:left;">
@include('app.pdf.invoice.partials.billing-address')
</div>
@if($invoice->user->billingaddress->name || $invoice->user->billingaddress->address_street_1 || $invoice->user->billingaddress->address_street_2 || $invoice->user->billingaddress->country || $invoice->user->billingaddress->state || $invoice->user->billingaddress->city || $invoice->user->billingaddress->zip || $invoice->user->billingaddress->phone)
@if($invoice->user->billingaddress)
<div style="float:right;">
@else
<div style="float:left;">

View File

@ -1,7 +1,5 @@
@if($invoice->user->billingaddress)
@if($invoice->user->billingaddress->name || $invoice->user->billingaddress->address_street_1 || $invoice->user->billingaddress->address_street_2 || $invoice->user->billingaddress->country || $invoice->user->billingaddress->state || $invoice->user->billingaddress->city || $invoice->user->billingaddress->zip || $invoice->user->billingaddress->phone)
<p class="bill-to">Bill To,</p>
@endif
<p class="bill-to">Bill To,</p>
@if($invoice->user->billingaddress->name)
<p class="bill-user-name">
{{$invoice->user->billingaddress->name}}

View File

@ -3,6 +3,6 @@
<div class="notes-label">
Notes
</div>
{{$invoice->notes}}
{!! $invoice->notes !!}
</div>
@endif

View File

@ -1,7 +1,5 @@
@if($invoice->user->shippingaddress)
@if($invoice->user->shippingaddress->name || $invoice->user->shippingaddress->address_street_1 || $invoice->user->shippingaddress->address_street_2 || $invoice->user->shippingaddress->country || $invoice->user->shippingaddress->state || $invoice->user->shippingaddress->city || $invoice->user->shippingaddress->zip || $invoice->user->phone)
<p class="ship-to">Ship To,</p>
@endif
<p class="ship-to">Ship To,</p>
@if($invoice->user->shippingaddress->name)
<p class="ship-user-name">
{{$invoice->user->shippingaddress->name}}

View File

@ -18,24 +18,47 @@
@endphp
@foreach ($invoice->items as $item)
<tr class="item-details">
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 20px; vertical-align: top;">{{$index}}</td>
<td class="inv-item items" style="text-align: left; color: #040405;padding-left: 0px">
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 20px; vertical-align: top;"
>
{{$index}}
</td>
<td
class="inv-item items"
style="text-align: left; color: #040405;padding-left: 0px"
>
<span>{{ $item->name }}</span><br>
<span style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;">{{ $item->description }}</span>
</td>
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 20px">{{$item->quantity}}</td>
<td class="inv-item items" style="text-align: right; color: #040405; padding-right: 40px">{{$item->price/100}}</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 20px"
>
{{$item->quantity}}
</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 40px"
>
{!! format_money_pdf($item->price, $invoice->user->currency) !!}
</td>
@if($invoice->discount_per_item === 'YES')
<td class="inv-item items" style="text-align: right; color: #040405; padding-left: 10px">
@if($item->discount_type === 'fixed')
{{$item->discount_val/100}}
{!! format_money_pdf($item->discount_val, $invoice->user->currency) !!}
@endif
@if($item->discount_type === 'percentage')
{{$item->discount}}%
@endif
</td>
@endif
<td class="inv-item items" style="text-align: right; color: #040405;">{{$item->total/100}}</td>
<td
class="inv-item items"
style="text-align: right; color: #040405;"
>
{!! format_money_pdf($item->total, $invoice->user->currency) !!}
</td>
</tr>
@php
$index += 1
@ -47,7 +70,7 @@
<tr>
<td class="no-borde" style="color: #55547A; padding-left:10px; font-size:12px;">Subtotal</td>
<td class="no-border items"
style="padding-right:10px; text-align: right; font-size:12px; color: #040405; font-weight: 500;">{!! format_money_pdf($invoice->sub_total) !!}</td>
style="padding-right:10px; text-align: right; font-size:12px; color: #040405; font-weight: 500;">{!! format_money_pdf($invoice->sub_total, $invoice->user->currency) !!}</td>
</tr>
@if ($invoice->tax_per_item === 'YES')
@ -57,7 +80,7 @@
{{$labels[$i]}}
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
{!! format_money_pdf($taxes[$i]) !!}
{!! format_money_pdf($taxes[$i], $invoice->user->currency) !!}
</td>
</tr>
@endfor
@ -68,7 +91,7 @@
{{$tax->name.' ('.$tax->percent.'%)'}}
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
{!! format_money_pdf($tax->amount) !!}
{!! format_money_pdf($tax->amount, $invoice->user->currency) !!}
</td>
</tr>
@endforeach
@ -77,14 +100,19 @@
@if ($invoice->discount_per_item === 'NO')
<tr>
<td class="no-border" style="padding-left:10px; text-align:left; font-size:12px; color: #55547A;">
Discount ({{$invoice->discount}}%)
@if($invoice->discount_type === 'fixed')
Discount
@endif
@if($invoice->discount_type === 'percentage')
Discount ({{$invoice->discount}}%)
@endif
</td>
<td class="no-border items padd2" style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; color: #040405">
@if($invoice->discount_type === 'fixed')
{!! format_money_pdf($invoice->discount_val) !!}
{!! format_money_pdf($invoice->discount_val, $invoice->user->currency) !!}
@endif
@if($invoice->discount_type === 'percentage')
{!! format_money_pdf($invoice->discount_val) !!}
{!! format_money_pdf($invoice->discount_val, $invoice->user->currency) !!}
@endif
</td>
</tr>
@ -103,7 +131,7 @@
class="no-border total-border-right items padd8"
style="padding-right:10px; font-weight: 500; text-align: right; font-size:12px; padding-top:20px; color: #5851DB"
>
{!! format_money_pdf($invoice->total)!!}
{!! format_money_pdf($invoice->total, $invoice->user->currency)!!}
</td>
</tr>
</table>

View File

@ -5,7 +5,7 @@
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
/* html {
@ -181,10 +181,14 @@
@foreach ($expenseCategories as $expenseCategory)
<tr>
<td>
<p class="expense-title">{{ $expenseCategory->category->name }}</p>
<p class="expense-title">
{{ $expenseCategory->category->name }}
</p>
</td>
<td>
<p class="expense-money">{!! format_money_pdf($expenseCategory->total_amount) !!}</p>
<p class="expense-money">
{!! format_money_pdf($expenseCategory->total_amount) !!}
</p>
</td>
</tr>
@endforeach

View File

@ -5,7 +5,7 @@
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
html {
@ -220,10 +220,14 @@
@foreach ($expenseCategories as $expenseCategory)
<tr>
<td>
<p class="expense-title">{{ $expenseCategory->category->name }}</p>
<p class="expense-title">
{{ $expenseCategory->category->name }}
</p>
</td>
<td>
<p class="expense-money">{!! format_money_pdf($expenseCategory->total_amount) !!}</p>
<p class="expense-money">
{!! format_money_pdf($expenseCategory->total_amount) !!}
</p>
</td>
</tr>
@endforeach

View File

@ -5,7 +5,7 @@
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
/* html {
@ -223,10 +223,14 @@
@foreach ($customer->invoices as $invoice)
<tr>
<td>
<p class="expense-title">{{ $invoice->formattedInvoiceDate }} ({{ $invoice->invoice_number }})</p>
<p class="expense-title">
{{ $invoice->formattedInvoiceDate }} ({{ $invoice->invoice_number }})
</p>
</td>
<td>
<p class="expense-money">{!! format_money_pdf($invoice->total) !!}</p>
<p class="expense-money">
{!! format_money_pdf($invoice->total) !!}
</p>
</td>
</tr>
@endforeach
@ -235,7 +239,9 @@
<table class="expense-total-table">
<tr>
<td class="expense-total-cell">
<p class="expense-total">{!! format_money_pdf($customer->totalAmount) !!}</p>
<p class="expense-total">
{!! format_money_pdf($customer->totalAmount) !!}
</p>
</td>
</tr>
</table>
@ -249,7 +255,9 @@
<p class="profit-title">TOTAL SALES</p>
</td>
<td>
<p class="profit-money">{!! format_money_pdf($totalAmount) !!}</p>
<p class="profit-money">
{!! format_money_pdf($totalAmount) !!}
</p>
</td>
</tr>
</table>

View File

@ -5,7 +5,7 @@
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
/* html {
@ -222,10 +222,14 @@
<table class="expenses-table">
<tr>
<td>
<p class="expense-title">{{ $item->name }}</p>
<p class="expense-title">
{{ $item->name }}
</p>
</td>
<td>
<p class="expense-money">{!! format_money_pdf($item->total_amount) !!}</p>
<p class="expense-money">
{!! format_money_pdf($item->total_amount) !!}
</p>
</td>
</tr>
</table>
@ -235,7 +239,9 @@
<table class="expense-total-table">
<tr>
<td class="expense-total-cell">
<p class="expense-total">{!! format_money_pdf($totalAmount) !!}</p>
<p class="expense-total">
{!! format_money_pdf($totalAmount) !!}
</p>
</td>
</tr>
</table>
@ -248,7 +254,9 @@
<p class="profit-title">TOTAL SALES</p>
</td>
<td>
<p class="profit-money">{!! format_money_pdf($totalAmount) !!}</p>
<p class="profit-money">
{!! format_money_pdf($totalAmount) !!}
</p>
</td>
</tr>
</table>

View File

@ -5,7 +5,7 @@
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-family: "DejaVu Sans";
}
/* html {
@ -174,10 +174,14 @@
<table class="header">
<tr>
<td>
<p class="heading-text">{{ $company->name }}</p>
<p class="heading-text">
{{ $company->name }}
</p>
</td>
<td>
<p class="heading-date-range">{{ $from_date }} - {{ $to_date }}</p>
<p class="heading-date-range">
{{ $from_date }} - {{ $to_date }}
</p>
</td>
</tr>
<tr>
@ -192,10 +196,14 @@
@foreach ($taxTypes as $tax)
<tr>
<td>
<p class="tax-title">{{ $tax->taxType->name }}</p>
<p class="tax-title">
{{ $tax->taxType->name }}
</p>
</td>
<td>
<p class="tax-money">{!! format_money_pdf($tax->total_tax_amount) !!}</p>
<p class="tax-money">
{!! format_money_pdf($tax->total_tax_amount) !!}
</p>
</td>
</tr>
@endforeach
@ -207,7 +215,9 @@
<table class="tax-total-table">
<tr>
<td class="tax-total-cell">
<p class="tax-total">{!! format_money_pdf($totalTaxAmount) !!}</p>
<p class="tax-total">
{!! format_money_pdf($totalTaxAmount) !!}
</p>
</td>
</tr>
</table>
@ -217,7 +227,9 @@
<p class="total-tax-title">TOTAL TAX</p>
</td>
<td>
<p class="total-tax-money">{!! format_money_pdf($totalTaxAmount) !!}</p>
<p class="total-tax-money">
{!! format_money_pdf($totalTaxAmount) !!}
</p>
</td>
</tr>
</table>

View File

@ -348,6 +348,16 @@ Route::group(['middleware' => 'api'], function () {
'uses' => 'CompanyController@updateSetting'
]);
Route::get('/get-customize-setting', [
'as' => 'admin.get.customize.setting',
'uses' => 'CompanyController@getCustomizeSetting'
]);
Route::put('/update-customize-setting', [
'as' => 'admin.update.customize.setting',
'uses' => 'CompanyController@updateCustomizeSetting'
]);
Route::get('/environment/mail', [
'as' => 'admin.environment.mail',
'uses' => 'EnvironmentController@getMailDrivers'