Skip to content

Commit

Permalink
feat: extract financing to it's own table & fix: Finance vehicle menu
Browse files Browse the repository at this point in the history
* feat: extract financing to it's own table

* refactor: update storage functions to use the vehicle_financing table

* refactor: use the finance function

* feat: migration file

* feat: get financed by plate & transfer check

* fix: passing the wrong argument

* feat: remove unused function

* feat: player_vehicles should be in qbx_vehicles, DEFAULT NULL & id as FK

* vehicleId as a primary key

* Update migrate.sql

* fix: a license can have multiple citizenid

* fix: finance input & finance menu

* Remove citizenId & plate from vehicle_financing

* revert old export

* use qbx_vehicles API & fix queries

* Update storage.lua

* use TinyInt

* feat: remove storage function in favor of qbx_vehicles export
  • Loading branch information
TonybynMp4 authored Mar 15, 2024
1 parent 557476e commit 560999d
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 102 deletions.
8 changes: 6 additions & 2 deletions client/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ local function showFinancedVehiclesMenu()

if vehicles == nil or #vehicles == 0 then return exports.qbx_core:Notify(locale('error.nofinanced'), 'error') end
for _, v in pairs(vehicles) do
if v.balance ~= 0 then
if v.balance and v.balance > 0 then
local name = VEHICLES[v.vehicle].name
local plate = v.plate:upper()
ownedVehicles[#ownedVehicles + 1] = {
Expand Down Expand Up @@ -152,14 +152,18 @@ end
---@param targetShowroomVehicle integer vehicleName
---@param buyVehicle string model
local function openFinance(targetShowroomVehicle, buyVehicle)
local dialog = lib.inputDialog(VEHICLES[buyVehicle].name:upper()..' '..buyVehicle:upper()..' - $'..getVehPrice(targetShowroomVehicle), {
local dialog = lib.inputDialog(VEHICLES[buyVehicle].brand:upper()..' '..VEHICLES[buyVehicle].name:upper()..' - $'..getVehPrice(targetShowroomVehicle), {
{
type = 'number',
label = locale('menus.financesubmit_downpayment')..sharedConfig.finance.minimumDown..'%',
min = VEHICLES[buyVehicle].price * sharedConfig.finance.minimumDown / 100,
max = VEHICLES[buyVehicle].price
},
{
type = 'number',
label = locale('menus.financesubmit_totalpayment')..sharedConfig.finance.maximumPayments,
min = 2,
max = sharedConfig.finance.maximumPayments
}
})

Expand Down
4 changes: 3 additions & 1 deletion fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ files {
'locales/*.json'
}

dependency 'qbx_vehicles'

provide 'qb-vehicleshop'
lua54 'yes'
use_experimental_fxv2_oal 'yes'
use_experimental_fxv2_oal 'yes'
20 changes: 20 additions & 0 deletions migrate.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS `vehicle_financing` (
`vehicleId` int(11) NOT NULL,
`balance` int(11) DEFAULT NULL,
`paymentamount` int(11) DEFAULT NULL,
`paymentsleft` tinyint(4) DEFAULT NULL,
`financetime` int(11) DEFAULT NULL,
PRIMARY KEY (`vehicleId`),
FOREIGN KEY `vehicleId` (`vehicleId`) REFERENCES `player_vehicles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO vehicle_financing (vehicleId, balance, paymentamount, paymentsleft, financetime)
SELECT id, balance, paymentamount, paymentsleft, financetime
FROM player_vehicles
WHERE balance > 0 OR paymentamount > 0 OR paymentsleft > 0 OR financetime > 0;

ALTER TABLE player_vehicles
DROP COLUMN balance,
DROP COLUMN paymentamount,
DROP COLUMN paymentsleft,
DROP COLUMN financetime;
43 changes: 24 additions & 19 deletions server/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ RegisterNetEvent('qbx_vehicleshop:server:removePlayer', function(citizenid)
if not financeTimer[citizenid] then return end

local playTime = financeTimer[citizenid]
local financetime = FetchVehicleEntitiesByCitizenId(citizenid)
for _, v in pairs(financetime) do
local vehicles = FetchFinancedVehicleEntitiesByCitizenId(citizenid)
for _, v in pairs(vehicles) do
if v.balance >= 1 then
local newTime = math.floor(v.financetime - (((os.time() - playTime) / 1000) / 60))
if newTime < 0 then newTime = 0 end
UpdateVehicleEntityFinanceTime(newTime, v.plate)
UpdateVehicleEntityFinanceTime(newTime, v.vehicleId)
end
end
financeTimer[citizenid] = nil
Expand All @@ -32,14 +32,14 @@ AddEventHandler('playerDropped', function()
local src = source
local license = GetPlayerIdentifierByType(src, 'license2') or GetPlayerIdentifierByType(src, 'license')
if not license then return end
local vehicles = FetchVehicleEntitiesByLicense(license)
local vehicles = FetchFinancedVehicleEntitiesByLicense(license)
if not vehicles then return end
for _, v in pairs(vehicles) do
local playTime = financeTimer[v.citizenid]
if v.balance >= 1 and playTime then
local newTime = math.floor(v.financetime - (((os.time() - playTime) / 1000) / 60))
if newTime < 0 then newTime = 0 end
UpdateVehicleEntityFinanceTime(newTime, v.plate)
UpdateVehicleEntityFinanceTime(newTime, v.vehicleId)
end
end
if vehicles[1] and financeTimer[vehicles[1].citizenid] then
Expand Down Expand Up @@ -94,6 +94,13 @@ lib.callback.register('qbx_vehicleshop:server:GetVehiclesByName', function(sourc
local player = exports.qbx_core:GetPlayer(src)
if not player then return end
local vehicles = FetchVehicleEntitiesByCitizenId(player.PlayerData.citizenid)
local financeVehicles = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid)
for _, v in pairs(financeVehicles) do
vehicles[v.vehicleId].balance = v.balance
vehicles[v.vehicleId].paymentamount = v.paymentamount
vehicles[v.vehicleId].paymentsleft = v.paymentsleft
vehicles[v.vehicleId].financetime = v.financetime
end
if vehicles[1] then
return vehicles
end
Expand Down Expand Up @@ -237,11 +244,9 @@ RegisterNetEvent('qbx_vehicleshop:server:buyShowroomVehicle', function(vehicle)
return
end

local cid = player.PlayerData.citizenid
local plate = generatePlate()
InsertVehicleEntity({
license = player.PlayerData.license,
citizenId = cid,
exports.qbx_vehicles:CreateVehicleEntity({
citizenId = player.PlayerData.citizenid,
model = vehicle,
plate = plate,
})
Expand Down Expand Up @@ -282,7 +287,6 @@ RegisterNetEvent('qbx_vehicleshop:server:financeVehicle', function(downPayment,

InsertVehicleEntityWithFinance({
insertVehicleEntityRequest = {
license = player.PlayerData.license,
citizenId = cid,
model = vehicle,
plate = plate,
Expand Down Expand Up @@ -343,8 +347,7 @@ RegisterNetEvent('qbx_vehicleshop:server:sellShowroomVehicle', function(data, pl

if not sellShowroomVehicleTransact(src, target, vehiclePrice, vehiclePrice) then return end

InsertVehicleEntity({
license = target.PlayerData.license,
exports.qbx_vehicles:CreateVehicleEntity({
citizenId = cid,
model = vehicle,
plate = plate
Expand Down Expand Up @@ -390,7 +393,6 @@ RegisterNetEvent('qbx_vehicleshop:server:sellfinanceVehicle', function(downPayme

InsertVehicleEntityWithFinance({
insertVehicleEntityRequest = {
license = target.PlayerData.license,
citizenId = cid,
model = vehicle,
plate = plate,
Expand Down Expand Up @@ -418,8 +420,8 @@ RegisterNetEvent('qbx_vehicleshop:server:checkFinance', function()
local vehicles = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid)
for _, v in pairs(vehicles) do
local plate = v.plate
DeleteVehicleEntity(plate)
--MySQL.update('UPDATE player_vehicles SET citizenid = ? WHERE plate = ?', {'REPO-'..v.citizenid, plate}) -- Use this if you don't want them to be deleted
DeleteVehicleEntity(v.id)
--MySQL.update('UPDATE player_vehicles SET citizenid = ? WHERE id = ?', {'REPO-'..v.citizenid, v.id}) -- Use this if you don't want them to be deleted
exports.qbx_core:Notify(src, locale('error.repossessed', plate), 'error')
end
end)
Expand Down Expand Up @@ -457,8 +459,11 @@ lib.addCommand('transfervehicle', {help = locale('general.command_transfervehicl
local player = exports.qbx_core:GetPlayer(src)
local target = exports.qbx_core:GetPlayer(buyerId)
local row = FetchVehicleEntityByPlate(plate)
if config.finance.preventSelling and row.balance > 0 then
return exports.qbx_core:Notify(src, locale('error.financed'), 'error')
if config.finance.preventSelling then
local financeRow = FetchFinancedVehicleEntityById(row.id)
if financeRow.balance > 0 then
return exports.qbx_core:Notify(src, locale('error.financed'), 'error')
end
end
if row.citizenid ~= player.PlayerData.citizenid then
return exports.qbx_core:Notify(src, locale('error.notown'), 'error')
Expand Down Expand Up @@ -491,11 +496,11 @@ lib.addCommand('transfervehicle', {help = locale('general.command_transfervehicl
player.Functions.AddMoney(currencyType, sellAmount)
target.Functions.RemoveMoney(currencyType, sellAmount)
end
UpdateVehicleEntityOwner(targetcid, targetlicense, plate)
UpdateVehicleEntityOwner(targetcid, targetlicense, row.id)
TriggerClientEvent('vehiclekeys:client:SetOwner', buyerId, plate)
local sellerMessage = sellAmount > 0 and locale('success.soldfor') .. lib.math.groupdigits(sellAmount) or locale('success.gifted')
local buyerMessage = sellAmount > 0 and locale('success.boughtfor') .. lib.math.groupdigits(sellAmount) or locale('success.received_gift')
exports.qbx_core:Notify(src, sellerMessage, 'success')
exports.qbx_core:Notify(buyerId, buyerMessage, 'success')
end, GetEntityModel(vehicle), sellAmount)
end)
end)
80 changes: 38 additions & 42 deletions server/storage.lua
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
---@class InsertVehicleEntityRequest
---@field license string
---@field citizenId string
---@field model string
---@field plate string

---@param request InsertVehicleEntityRequest
function InsertVehicleEntity(request)
MySQL.insert('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, garage, state) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {
request.license,
request.citizenId,
request.model,
joaat(request.model),
'{}',
request.plate,
'pillboxgarage',
0
})
end

---@class VehicleFinanceServer
---@field balance number
---@field payment number
Expand All @@ -30,15 +15,13 @@ end

---@param request InsertVehicleEntityWithFinanceRequest
function InsertVehicleEntityWithFinance(request)
MySQL.insert('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, garage, state, balance, paymentamount, paymentsleft, financetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', {
request.insertVehicleEntityRequest.license,
request.insertVehicleEntityRequest.citizenId,
request.insertVehicleEntityRequest.model,
joaat(request.insertVehicleEntityRequest.model),
'{}',
request.insertVehicleEntityRequest.plate,
'pillboxgarage',
0,
local vehicleId = exports.qbx_vehicles:CreateVehicleEntity({
citizenId = request.insertVehicleEntityRequest.citizenId,
model = request.insertVehicleEntityRequest.model,
plate = request.insertVehicleEntityRequest.plate
})
MySQL.insert('INSERT INTO vehicle_financing (vehicleId, balance, paymentamount, paymentsleft, financetime) VALUES (?, ?, ?, ?, ?)', {
vehicleId,
request.vehicleFinance.balance,
request.vehicleFinance.payment,
request.vehicleFinance.paymentsLeft,
Expand All @@ -48,18 +31,19 @@ end

---@alias VehicleEntity table

---@class VehicleFinancingEntity
---@field vehicleId integer
---@field balance number
---@field paymentamount number
---@field paymentsleft integer
---@field financetime number

---@param citizenId string
---@return VehicleEntity[]
function FetchVehicleEntitiesByCitizenId(citizenId)
return MySQL.query.await('SELECT * FROM player_vehicles WHERE citizenid = ?', {citizenId})
end

---@param license string
---@return VehicleEntity[]
function FetchVehicleEntitiesByLicense(license)
return MySQL.query.await('SELECT * FROM player_vehicles WHERE license = ?', {license})
end

---@param plate string
---@return VehicleEntity
function FetchVehicleEntityByPlate(plate)
Expand All @@ -74,15 +58,15 @@ function DoesVehicleEntityExist(plate)
end

---@param time number
---@param plate string
function UpdateVehicleEntityFinanceTime(time, plate)
MySQL.update('UPDATE player_vehicles SET financetime = ? WHERE plate = ?', {time, plate})
---@param vehicleId integer
function UpdateVehicleEntityFinanceTime(time, vehicleId)
MySQL.update('UPDATE vehicle_financing SET financetime = ? WHERE vehicleId = ?', {time, vehicleId})
end

---@param vehicleFinance VehicleFinanceServer
---@param plate string
function UpdateVehicleFinance(vehicleFinance, plate)
MySQL.update('UPDATE player_vehicles SET balance = ?, paymentamount = ?, paymentsleft = ?, financetime = ? WHERE plate = ?', {
MySQL.update('UPDATE vehicle_financing AS vf INNER JOIN player_vehicles AS pv ON vf.vehicleId = pv.id SET vf.balance = ?, vf.paymentamount = ?, vf.paymentsleft = ?, vf.financetime = ? WHERE pv.plate = ?', {
vehicleFinance.balance,
vehicleFinance.payment,
vehicleFinance.paymentsLeft,
Expand All @@ -93,18 +77,30 @@ end

---@param citizenId string
---@param license string
---@param plate string
function UpdateVehicleEntityOwner(citizenId, license, plate)
MySQL.update('UPDATE player_vehicles SET citizenid = ?, license = ? WHERE plate = ?', {citizenId, license, plate})
---@param vehicleId integer
function UpdateVehicleEntityOwner(citizenId, license, vehicleId)
MySQL.update('UPDATE player_vehicles SET citizenid = ?, license = ? WHERE id = ?', {citizenId, license, vehicleId})
end

---@param id integer
---@return VehicleFinancingEntity
function FetchFinancedVehicleEntityById(id)
return MySQL.single.await('SELECT * FROM vehicle_financing WHERE vehicleId = ? AND balance > 0 AND financetime < 1', {id})
end

---@param citizenId string
---@return VehicleEntity[]
---@return VehicleFinancingEntity
function FetchFinancedVehicleEntitiesByCitizenId(citizenId)
return MySQL.query.await('SELECT * FROM player_vehicles WHERE citizenid = ? AND balance > 0 AND financetime < 1', {citizenId})
return MySQL.query.await('SELECT vehicle_financing.* FROM vehicle_financing INNER JOIN player_vehicles ON player_vehicles.citizenid = ? WHERE vehicle_financing.vehicleId = player_vehicles.id AND vehicle_financing.balance > 0 AND vehicle_financing.financetime > 1', {citizenId})
end

---@param plate string
function DeleteVehicleEntity(plate)
MySQL.query('DELETE FROM player_vehicles WHERE plate = ?', {plate})
---@param license string
---@return VehicleFinancingEntity
function FetchFinancedVehicleEntitiesByLicense(license)
return MySQL.query.await('SELECT vf.*, p.citizenid FROM vehicle_financing AS vf INNER JOIN players AS p ON p.citizenid = ? INNER JOIN player_vehicles AS pv ON pv.citizenid = p.citizenid AND vf.balance > 0 AND vf.financetime < 1', {license})
end

---@param vehicleId integer
function DeleteVehicleEntity(vehicleId)
exports.qbx_vehicles:DeleteEntityById(vehicleId)
end
47 changes: 9 additions & 38 deletions vehshop.sql
Original file line number Diff line number Diff line change
@@ -1,38 +1,9 @@
CREATE TABLE IF NOT EXISTS `player_vehicles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`license` varchar(50) DEFAULT NULL,
`citizenid` varchar(50) DEFAULT NULL,
`vehicle` varchar(50) DEFAULT NULL,
`hash` varchar(50) DEFAULT NULL,
`mods` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`plate` varchar(15) NOT NULL,
`fakeplate` varchar(50) DEFAULT NULL,
`garage` varchar(50) DEFAULT 'pillboxgarage',
`fuel` int(11) DEFAULT 100,
`engine` float DEFAULT 1000,
`body` float DEFAULT 1000,
`state` int(11) DEFAULT 1,
`depotprice` int(11) NOT NULL DEFAULT 0,
`drivingdistance` int(50) DEFAULT NULL,
`status` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `plate` (`plate`),
KEY `citizenid` (`citizenid`),
KEY `license` (`license`)
) ENGINE=InnoDB AUTO_INCREMENT=1;

ALTER TABLE `player_vehicles`
ADD UNIQUE INDEX UK_playervehicles_plate (plate);

ALTER TABLE `player_vehicles`
ADD CONSTRAINT FK_playervehicles_players FOREIGN KEY (citizenid)
REFERENCES `players` (citizenid) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `player_vehicles`
ADD COLUMN `balance` int(11) NOT NULL DEFAULT 0;
ALTER TABLE `player_vehicles`
ADD COLUMN `paymentamount` int(11) NOT NULL DEFAULT 0;
ALTER TABLE `player_vehicles`
ADD COLUMN `paymentsleft` int(11) NOT NULL DEFAULT 0;
ALTER TABLE `player_vehicles`
ADD COLUMN `financetime` int(11) NOT NULL DEFAULT 0;
CREATE TABLE IF NOT EXISTS `vehicle_financing` (
`vehicleId` int(11) NOT NULL,
`balance` int(11) DEFAULT NULL,
`paymentamount` int(11) DEFAULT NULL,
`paymentsleft` int(11) DEFAULT NULL,
`financetime` int(11) DEFAULT NULL,
PRIMARY KEY (`vehicleId`),
FOREIGN KEY `vehicleId` (`vehicleId`) REFERENCES `player_vehicles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

0 comments on commit 560999d

Please sign in to comment.