Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117413268
D1168.1774829441.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
D1168.1774829441.diff
View Options
diff --git a/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Controllers\API\V4\Admin;
+
+use App\Discount;
+use App\Http\Controllers\Controller;
+
+class DiscountsController extends Controller
+{
+ /**
+ * Returns (active) discounts defined in the system.
+ *
+ * @return \Illuminate\Http\JsonResponse JSON response
+ */
+ public function index()
+ {
+ $discounts = [];
+
+ Discount::where('active', true)->orderBy('discount')->get()
+ ->map(function ($discount) use (&$discounts) {
+ $label = $discount->discount . '% - ' . $discount->description;
+
+ if ($discount->code) {
+ $label .= " [{$discount->code}]";
+ }
+
+ $discounts[] = [
+ 'id' => $discount->id,
+ 'discount' => $discount->discount,
+ 'code' => $discount->code,
+ 'description' => $discount->description,
+ 'label' => $label,
+ ];
+ });
+
+ return response()->json([
+ 'status' => 'success',
+ 'list' => $discounts,
+ 'count' => count($discounts),
+ ]);
+ }
+}
diff --git a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
@@ -2,6 +2,48 @@
namespace App\Http\Controllers\API\V4\Admin;
+use App\Discount;
+use App\Wallet;
+use Illuminate\Http\Request;
+
class WalletsController extends \App\Http\Controllers\API\V4\WalletsController
{
+ /**
+ * Update wallet data.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @params string $id Wallet identifier
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function update(Request $request, $id)
+ {
+ $wallet = Wallet::find($id);
+
+ if (empty($wallet)) {
+ return $this->errorResponse(404);
+ }
+
+ if (array_key_exists('discount', $request->input())) {
+ if (empty($request->discount)) {
+ $wallet->discount()->dissociate();
+ $wallet->save();
+ } elseif ($discount = Discount::find($request->discount)) {
+ $wallet->discount()->associate($discount);
+ $wallet->save();
+ }
+ }
+
+ $response = $wallet->toArray();
+
+ if ($wallet->discount) {
+ $response['discount'] = $wallet->discount->discount;
+ $response['discount_description'] = $wallet->discount->description;
+ }
+
+ $response['status'] = 'success';
+ $response['message'] = \trans('app.wallet-update-success');
+
+ return response()->json($response);
+ }
}
diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/WalletsController.php
@@ -13,7 +13,7 @@
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function index()
{
@@ -23,7 +23,7 @@
/**
* Show the form for creating a new resource.
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function create()
{
@@ -35,7 +35,7 @@
*
* @param \Illuminate\Http\Request $request
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
@@ -47,7 +47,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function show($id)
{
@@ -59,7 +59,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function edit($id)
{
@@ -72,7 +72,7 @@
* @param \Illuminate\Http\Request $request
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function update(Request $request, $id)
{
@@ -84,7 +84,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -27,4 +27,6 @@
'search-foundxdomains' => ':x domains have been found.',
'search-foundxusers' => ':x user accounts have been found.',
+
+ 'wallet-update-success' => 'User wallet updated successfully.',
];
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -110,7 +110,7 @@
<div class="col-sm-10">
<span class="form-control-plaintext" id="discount">
<span>{{ wallet_discount ? (wallet_discount + '% - ' + wallet_discount_description) : 'none' }}</span>
- <button type="button" class="btn btn-secondary btn-sm">Edit</button>
+ <button type="button" class="btn btn-secondary btn-sm" @click="discountEdit">Edit</button>
</span>
</div>
</div>
@@ -223,6 +223,33 @@
</div>
</div>
</div>
+
+ <div id="discount-dialog" class="modal" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">Account discount</h5>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <p class="form-group">
+ <select v-model="wallet_discount_id" class="custom-select">
+ <option value="">- none -</option>
+ <option v-for="item in discounts" :value="item.id" :key="item.id">{{ item.label }}</option>
+ </select>
+ </p>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">Cancel</button>
+ <button type="button" class="btn btn-primary modal-action" @click="submitDiscount()">
+ <svg-icon icon="check"></svg-icon> Submit
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</template>
@@ -240,8 +267,10 @@
balance: 0,
discount: 0,
discount_description: '',
+ discounts: [],
wallet_discount: 0,
wallet_discount_description: '',
+ wallet_discount_id: '',
domains: [],
skus: [],
users: [],
@@ -281,6 +310,7 @@
})
this.wallet_discount = this.user.wallets[0].discount
+ this.wallet_discount_id = this.user.wallets[0].discount_id || ''
this.wallet_discount_description = this.user.wallets[0].discount_description
// Create subscriptions list
@@ -293,6 +323,8 @@
let item = {
id: sku.id,
name: sku.name,
+ cost: sku.cost,
+ units: count - sku.units_free,
price: this.price(sku.cost, count - sku.units_free)
}
@@ -329,6 +361,21 @@
})
},
methods: {
+ discountEdit() {
+ $('#discount-dialog')
+ .on('shown.bs.modal', (e, a) => {
+ $(e.target).find('select').focus()
+ })
+ .modal()
+
+ if (!this.discounts.length) {
+ // Fetch discounts
+ axios.get('/api/v4/discounts')
+ .then(response => {
+ this.discounts = response.data.list
+ })
+ }
+ },
price(cost, units = 1) {
let index = ''
@@ -342,7 +389,30 @@
}
return this.$root.price(cost * units) + '/month' + index
- }
+ },
+ submitDiscount() {
+ let dialog = $('#discount-dialog').modal('hide')
+
+ axios.put('/api/v4/wallets/' + this.user.wallets[0].id, { discount: this.wallet_discount_id })
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toastr('success', response.data.message)
+ this.wallet_discount = response.data.discount
+ this.wallet_discount_id = response.data.discount_id || ''
+ this.wallet_discount_description = response.data.discount_description
+
+ // Update prices in Subscriptions tab
+ if (this.user.wallet.id == response.data.id) {
+ this.discount = this.wallet_discount
+ this.discount_description = this.wallet_discount_description
+
+ this.skus.forEach(sku => {
+ sku.price = this.price(sku.cost, sku.units)
+ })
+ }
+ }
+ })
+ },
}
}
</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -94,5 +94,6 @@
Route::apiResource('skus', API\V4\Admin\SkusController::class);
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
+ Route::apiResource('discounts', API\V4\Admin\DiscountsController::class);
}
);
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php
--- a/src/tests/Browser/Admin/UserTest.php
+++ b/src/tests/Browser/Admin/UserTest.php
@@ -4,6 +4,7 @@
use App\Discount;
use Tests\Browser;
+use Tests\Browser\Components\Dialog;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Admin\User as UserPage;
use Tests\Browser\Pages\Dashboard;
@@ -339,4 +340,63 @@
});
});
}
+
+ /**
+ * Test editing wallet discount
+ *
+ * @depends testUserInfo2
+ */
+ public function testWalletDiscount(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $john = $this->getTestUser('john@kolab.org');
+
+ $browser->visit(new UserPage($john->id))
+ ->pause(100)
+ ->click('@user-finances #discount button')
+ // Test dialog content, and closing it with Cancel button
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->assertSeeIn('@title', 'Account discount')
+ ->assertFocused('@body select')
+ ->assertSelected('@body select', '')
+ ->assertSeeIn('@button-cancel', 'Cancel')
+ ->assertSeeIn('@button-action', 'Submit')
+ ->click('@button-cancel');
+ })
+ ->assertMissing('#discount-dialog')
+ ->click('@user-finances #discount button')
+ // Change the discount
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->click('@body select')
+ ->click('@body select option:nth-child(2)')
+ ->click('@button-action');
+ })
+ ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.')
+ ->assertSeeIn('#discount span', '10% - Test voucher')
+ ->click('@nav #tab-subscriptions')
+ ->with('@user-subscriptions', function (Browser $browser) {
+ $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹')
+ ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹')
+ ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹')
+ ->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher');
+ })
+ // Change back to 'none'
+ ->click('@nav #tab-finances')
+ ->click('@user-finances #discount button')
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->click('@body select')
+ ->click('@body select option:nth-child(1)')
+ ->click('@button-action');
+ })
+ ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.')
+ ->assertSeeIn('#discount span', 'none')
+ ->click('@nav #tab-subscriptions')
+ ->with('@user-subscriptions', function (Browser $browser) {
+ $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '4,44 CHF/month')
+ ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month')
+ ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '5,55 CHF/month')
+ ->assertMissing('table + .hint');
+ });
+ });
+ }
}
diff --git a/src/tests/Feature/Controller/Admin/DiscountsTest.php b/src/tests/Feature/Controller/Admin/DiscountsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/DiscountsTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Tests\Feature\Controller\Admin;
+
+use App\Discount;
+use Tests\TestCase;
+
+class DiscountsTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ self::useAdminUrl();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ }
+
+ /**
+ * Test listing discounts (/api/v4/discounts)
+ */
+ public function testIndex(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+
+ // Non-admin user
+ $response = $this->actingAs($user)->get("api/v4/discounts");
+ $response->assertStatus(403);
+
+ // Admin user
+ $response = $this->actingAs($admin)->get("api/v4/discounts");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $discount_test = Discount::where('code', 'TEST')->first();
+ $discount_free = Discount::where('discount', 100)->first();
+
+ $this->assertSame(3, $json['count']);
+ $this->assertSame($discount_test->id, $json['list'][0]['id']);
+ $this->assertSame($discount_test->discount, $json['list'][0]['discount']);
+ $this->assertSame($discount_test->code, $json['list'][0]['code']);
+ $this->assertSame($discount_test->description, $json['list'][0]['description']);
+ $this->assertSame('10% - Test voucher [TEST]', $json['list'][0]['label']);
+
+ $this->assertSame($discount_free->id, $json['list'][2]['id']);
+ $this->assertSame($discount_free->discount, $json['list'][2]['discount']);
+ $this->assertSame($discount_free->code, $json['list'][2]['code']);
+ $this->assertSame($discount_free->description, $json['list'][2]['description']);
+ $this->assertSame('100% - Free Account', $json['list'][2]['label']);
+ }
+}
diff --git a/src/tests/Feature/Controller/Admin/WalletsTest.php b/src/tests/Feature/Controller/Admin/WalletsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/WalletsTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Tests\Feature\Controller\Admin;
+
+use App\Discount;
+use Tests\TestCase;
+
+class WalletsTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ self::useAdminUrl();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ }
+
+ /**
+ * Test updating a wallet (PUT /api/v4/wallets/:id)
+ */
+ public function testUpdate(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $wallet = $user->wallets()->first();
+ $discount = Discount::where('code', 'TEST')->first();
+
+ // Non-admin user
+ $response = $this->actingAs($user)->put("api/v4/wallets/{$wallet->id}", []);
+ $response->assertStatus(403);
+
+ // Admin user - setting a discount
+ $post = ['discount' => $discount->id];
+ $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame('User wallet updated successfully.', $json['message']);
+ $this->assertSame($wallet->id, $json['id']);
+ $this->assertSame($discount->discount, $json['discount']);
+ $this->assertSame($discount->id, $json['discount_id']);
+ $this->assertSame($discount->description, $json['discount_description']);
+ $this->assertSame($discount->id, $wallet->fresh()->discount->id);
+
+ // Admin user - removing a discount
+ $post = ['discount' => null];
+ $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame('User wallet updated successfully.', $json['message']);
+ $this->assertSame($wallet->id, $json['id']);
+ $this->assertSame(null, $json['discount_id']);
+ $this->assertTrue(empty($json['discount_description']));
+ $this->assertSame(null, $wallet->fresh()->discount);
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 30, 12:10 AM (1 w, 17 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18721215
Default Alt Text
D1168.1774829441.diff (19 KB)
Attached To
Mode
D1168: Support: Editing a wallet discount
Attached
Detach File
Event Timeline