mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-05-25 12:44:55 +00:00
Invoice time support (#269)
* Changed invoice date to datetime * Fixed code style errors * Update TimeFormatsController.php * Update TimeFormatter.php * Update TimeFormatsController namespace * Fix missing comma in language file * Fix formatting --------- Co-authored-by: troky <troky2001@yahoo.com>
This commit is contained in:
committed by
GitHub
parent
32e03b98a3
commit
f52b73f517
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\V1\Admin\General;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Space\TimeFormatter;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TimeFormatsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
return response()->json([
|
||||
'time_formats' => TimeFormatter::get_list(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,16 @@ class CloneInvoiceController extends Controller
|
||||
|
||||
$exchange_rate = $invoice->exchange_rate;
|
||||
|
||||
$dateFormat = 'Y-m-d';
|
||||
$invoiceTimeEnabled = CompanySetting::getSetting(
|
||||
'invoice_use_time',
|
||||
$request->header('company')
|
||||
);
|
||||
|
||||
if ($invoiceTimeEnabled === 'YES') {
|
||||
$dateFormat .= ' H:i';
|
||||
}
|
||||
|
||||
$newInvoice = Invoice::create([
|
||||
'invoice_date' => $date->format('Y-m-d'),
|
||||
'due_date' => $due_date,
|
||||
|
||||
@@ -38,6 +38,15 @@ class CompanySettingRequest extends FormRequest
|
||||
'carbon_date_format' => [
|
||||
'required',
|
||||
],
|
||||
'moment_time_format' => [
|
||||
'required',
|
||||
],
|
||||
'carbon_time_format' => [
|
||||
'required',
|
||||
],
|
||||
'invoice_use_time' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,9 @@ class Company extends Model implements HasMedia
|
||||
'fiscal_year' => '1-12',
|
||||
'carbon_date_format' => 'Y/m/d',
|
||||
'moment_date_format' => 'YYYY/MM/DD',
|
||||
'carbon_time_format' => 'H:i',
|
||||
'moment_time_format' => 'HH:mm',
|
||||
'invoice_use_time' => 'NO',
|
||||
'notification_email' => 'noreply@invoiceshelf.com',
|
||||
'notify_invoice_viewed' => 'NO',
|
||||
'notify_estimate_viewed' => 'NO',
|
||||
|
||||
@@ -195,6 +195,12 @@ class Invoice extends Model implements HasMedia
|
||||
public function getFormattedInvoiceDateAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
$timeFormat = CompanySetting::getSetting('carbon_time_format', $this->company_id);
|
||||
$invoiceTimeEnabled = CompanySetting::getSetting('invoice_use_time', $this->company_id);
|
||||
|
||||
if ($invoiceTimeEnabled === 'YES') {
|
||||
$dateFormat .= ' '.$timeFormat;
|
||||
}
|
||||
|
||||
return Carbon::parse($this->invoice_date)->translatedFormat($dateFormat);
|
||||
}
|
||||
|
||||
34
app/Space/TimeFormatter.php
Normal file
34
app/Space/TimeFormatter.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Space;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class TimeFormatter
|
||||
{
|
||||
protected static $formats = [
|
||||
[
|
||||
'carbon_format' => 'H:i',
|
||||
'moment_format' => 'HH:mm',
|
||||
],
|
||||
[
|
||||
'carbon_format' => 'g:i a',
|
||||
'moment_format' => 'h:mm a',
|
||||
],
|
||||
];
|
||||
|
||||
public static function get_list()
|
||||
{
|
||||
$new = [];
|
||||
|
||||
foreach (static::$formats as $format) {
|
||||
$new[] = [
|
||||
'display_time' => Carbon::now()->format($format['carbon_format']),
|
||||
'carbon_format_value' => $format['carbon_format'],
|
||||
'moment_format_value' => $format['moment_format'],
|
||||
];
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->datetime('invoice_date')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->date('invoice_date')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -864,6 +864,7 @@
|
||||
"primary_currency": "Primary Currency",
|
||||
"timezone": "Time Zone",
|
||||
"date_format": "Date Format",
|
||||
"time_format": "Time Format",
|
||||
"currencies": {
|
||||
"title": "Currencies",
|
||||
"currency": "Currency | Currencies",
|
||||
@@ -990,6 +991,7 @@
|
||||
"delimiter_description": "Single character for specifying the boundary between 2 separate components. By default its set to -",
|
||||
"delimiter_param_label": "Delimiter Value",
|
||||
"date_format": "Date Format",
|
||||
"time_format": "Time Format",
|
||||
"date_format_description": "A local date and time field which accepts a format parameter. The default format: 'Y' renders the current year.",
|
||||
"date_format_param_label": "Format",
|
||||
"sequence": "Sequence",
|
||||
@@ -1234,6 +1236,7 @@
|
||||
"time_zone": "Time Zone",
|
||||
"fiscal_year": "Financial Year",
|
||||
"date_format": "Date Format",
|
||||
"time_format": "Time Fromat",
|
||||
"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 is added directly to the invoice.",
|
||||
@@ -1246,6 +1249,7 @@
|
||||
"select_language": "Select Language",
|
||||
"select_time_zone": "Select Time Zone",
|
||||
"select_date_format": "Select Date Format",
|
||||
"select_time_format": "Select Time Format",
|
||||
"select_financial_year": "Select Financial Year",
|
||||
"recurring_invoice_status": "Recurring Invoice Status",
|
||||
"create_status": "Create Status",
|
||||
@@ -1254,6 +1258,8 @@
|
||||
"update_status": "Update Status",
|
||||
"completed": "Completed",
|
||||
"company_currency_unchangeable": "Company currency cannot be changed",
|
||||
"invoice_use_time": "Use time in invoices",
|
||||
"invoice_use_time_description": "Enable this if you want to select exact invoice time.",
|
||||
"fiscal_years": {
|
||||
"january_december": "January - December",
|
||||
"february_january": "February - January",
|
||||
@@ -1422,6 +1428,7 @@
|
||||
"time_zone": "Time Zone",
|
||||
"fiscal_year": "Financial Year",
|
||||
"date_format": "Date Format",
|
||||
"time_format": "Time Format",
|
||||
"from_address": "From Address",
|
||||
"username": "Username",
|
||||
"next": "Next",
|
||||
|
||||
20
resources/scripts/admin/stores/global.js
vendored
20
resources/scripts/admin/stores/global.js
vendored
@@ -21,6 +21,7 @@ export const useGlobalStore = (useWindow = false) => {
|
||||
// Global Lists
|
||||
timeZones: [],
|
||||
dateFormats: [],
|
||||
timeFormats: [],
|
||||
currencies: [],
|
||||
countries: [],
|
||||
languages: [],
|
||||
@@ -156,6 +157,25 @@ export const useGlobalStore = (useWindow = false) => {
|
||||
})
|
||||
},
|
||||
|
||||
fetchTimeFormats() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.timeFormats.length) {
|
||||
resolve(this.timeFormats)
|
||||
} else {
|
||||
axios
|
||||
.get('/api/v1/time/formats')
|
||||
.then((response) => {
|
||||
this.timeFormats = response.data.time_formats
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
fetchTimeZones() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.timeZones.length) {
|
||||
|
||||
8
resources/scripts/admin/stores/invoice.js
vendored
8
resources/scripts/admin/stores/invoice.js
vendored
@@ -499,7 +499,13 @@ export const useInvoiceStore = (useWindow = false) => {
|
||||
this.newInvoice.sales_tax_address_type = companyStore.selectedCompanySettings.sales_tax_address_type
|
||||
this.newInvoice.discount_per_item =
|
||||
companyStore.selectedCompanySettings.discount_per_item
|
||||
this.newInvoice.invoice_date = moment().format('YYYY-MM-DD')
|
||||
|
||||
let dateFormat = 'YYYY-MM-DD';
|
||||
if (companyStore.selectedCompanySettings.invoice_use_time === 'YES') {
|
||||
dateFormat += ' HH:mm'
|
||||
}
|
||||
|
||||
this.newInvoice.invoice_date = moment().format(dateFormat)
|
||||
if (companyStore.selectedCompanySettings.invoice_set_due_date_automatically === 'YES') {
|
||||
this.newInvoice.due_date = moment()
|
||||
.add(companyStore.selectedCompanySettings.invoice_due_date_days, 'days')
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
:content-loading="isLoading"
|
||||
:calendar-button="true"
|
||||
calendar-button-icon="calendar"
|
||||
:enableTime="enableTime"
|
||||
:time24hr="time24h"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
@@ -61,8 +63,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import ExchangeRateConverter from '@/scripts/admin/components/estimate-invoice-common/ExchangeRateConverter.vue'
|
||||
import { useInvoiceStore } from '@/scripts/admin/stores/invoice'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
|
||||
const props = defineProps({
|
||||
v: {
|
||||
@@ -80,4 +84,17 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
const enableTime = computed(() => {
|
||||
return (
|
||||
companyStore.selectedCompanySettings.invoice_use_time === 'YES'
|
||||
);
|
||||
})
|
||||
const time24h = computed(() => {
|
||||
return (
|
||||
companyStore.selectedCompanySettings.carbon_time_format.indexOf('H') > -1
|
||||
);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -80,10 +80,11 @@
|
||||
label="display_date"
|
||||
value-prop="carbon_format_value"
|
||||
track-by="display_date"
|
||||
searchable
|
||||
:searchable="true"
|
||||
:invalid="v$.carbon_date_format.$error"
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
@@ -104,8 +105,36 @@
|
||||
class="w-full"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.preferences.time_format')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.carbon_time_format.$error &&
|
||||
v$.carbon_time_format.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="settingsForm.carbon_time_format"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.timeFormats"
|
||||
label="display_time"
|
||||
value-prop="carbon_format_value"
|
||||
track-by="display_time"
|
||||
:searchable="true"
|
||||
:invalid="v$.carbon_time_format.$error"
|
||||
class="w-full"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
|
||||
<BaseSwitchSection
|
||||
v-model="invoiceUseTimeField"
|
||||
:title="$t('settings.preferences.invoice_use_time')"
|
||||
:description="$t('settings.preferences.invoice_use_time_description')"
|
||||
/>
|
||||
|
||||
<BaseButton
|
||||
:content-loading="isFetchingInitialData"
|
||||
:disabled="isSaving"
|
||||
@@ -218,6 +247,33 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => settingsForm.carbon_time_format,
|
||||
(val) => {
|
||||
if (val) {
|
||||
const timeFormatObject = globalStore.timeFormats.find((d) => {
|
||||
return d.carbon_format_value === val
|
||||
})
|
||||
settingsForm.moment_time_format = timeFormatObject.moment_format_value
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const invoiceUseTimeField = computed({
|
||||
get: () => {
|
||||
return settingsForm.invoice_use_time === 'YES'
|
||||
},
|
||||
set: async (newValue) => {
|
||||
const value = newValue ? 'YES' : 'NO'
|
||||
let data = {
|
||||
settings: {
|
||||
invoice_use_time: value,
|
||||
},
|
||||
}
|
||||
settingsForm.invoice_use_time = value
|
||||
}
|
||||
})
|
||||
|
||||
const discountPerItemField = computed({
|
||||
get: () => {
|
||||
return settingsForm.discount_per_item === 'YES'
|
||||
@@ -271,12 +327,21 @@ const rules = computed(() => {
|
||||
moment_date_format: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
carbon_time_format: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
moment_time_format: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
time_zone: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
fiscal_year: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
invoice_use_time: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -292,6 +357,7 @@ async function setInitialData() {
|
||||
Promise.all([
|
||||
globalStore.fetchCurrencies(),
|
||||
globalStore.fetchDateFormats(),
|
||||
globalStore.fetchTimeFormats(),
|
||||
globalStore.fetchTimeZones(),
|
||||
]).then(([res1]) => {
|
||||
isFetchingInitialData.value = false
|
||||
|
||||
@@ -254,6 +254,14 @@ const carbonFormat = computed(() => {
|
||||
return companyStore.selectedCompanySettings?.carbon_date_format
|
||||
})
|
||||
|
||||
const carbonFormatWithTime = computed(() => {
|
||||
let format = companyStore.selectedCompanySettings?.carbon_date_format
|
||||
if (companyStore.selectedCompanySettings?.invoice_use_time === 'YES') {
|
||||
format += ' ' + companyStore.selectedCompanySettings?.carbon_time_format
|
||||
}
|
||||
return format.replace("g", "h").replace("a", "K");
|
||||
})
|
||||
|
||||
const hasIconSlot = computed(() => {
|
||||
return !!slots.icon
|
||||
})
|
||||
@@ -301,7 +309,7 @@ watch(
|
||||
config.altFormat = carbonFormat.value ? carbonFormat.value : 'd M Y'
|
||||
} else {
|
||||
config.altFormat = carbonFormat.value
|
||||
? `${carbonFormat.value} H:i `
|
||||
? `${carbonFormatWithTime.value}`
|
||||
: 'd M Y H:i'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ use App\Http\Controllers\V1\Admin\General\NotesController;
|
||||
use App\Http\Controllers\V1\Admin\General\NumberPlaceholdersController;
|
||||
use App\Http\Controllers\V1\Admin\General\SearchController;
|
||||
use App\Http\Controllers\V1\Admin\General\SearchUsersController;
|
||||
use App\Http\Controllers\V1\Admin\General\TimeFormatsController;
|
||||
use App\Http\Controllers\V1\Admin\General\TimezonesController;
|
||||
use App\Http\Controllers\V1\Admin\Invoice\ChangeInvoiceStatusController;
|
||||
use App\Http\Controllers\V1\Admin\Invoice\CloneInvoiceController;
|
||||
@@ -230,6 +231,8 @@ Route::prefix('/v1')->group(function () {
|
||||
|
||||
Route::get('/date/formats', DateFormatsController::class);
|
||||
|
||||
Route::get('/time/formats', TimeFormatsController::class);
|
||||
|
||||
Route::get('/next-number', NextNumberController::class);
|
||||
|
||||
Route::get('/number-placeholders', NumberPlaceholdersController::class);
|
||||
|
||||
Reference in New Issue
Block a user