|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
use App\Models\Concerns\Reportable;
|
|
|
|
use App\Tables\Changeset;
|
|
|
|
use App\Tables\Column;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
use MightyPork\Exceptions\NotApplicableException;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change proposal
|
|
|
|
*
|
|
|
|
* @property int $id
|
|
|
|
* @property \Carbon\Carbon $created_at
|
|
|
|
* @property \Carbon\Carbon $updated_at
|
|
|
|
* @property int $table_id
|
|
|
|
* @property int $revision_id
|
|
|
|
* @property int $author_id
|
|
|
|
* @property string $note
|
|
|
|
* @property Changeset $changeset - JSONB
|
|
|
|
* @property-read User $author
|
|
|
|
* @property-read Table $table
|
|
|
|
* @property-read Revision $revision
|
|
|
|
*/
|
|
|
|
class Proposal extends BaseModel
|
|
|
|
{
|
|
|
|
use Reportable;
|
|
|
|
protected $guarded = [];
|
|
|
|
|
|
|
|
protected $touches = ['author', 'table'];
|
|
|
|
|
|
|
|
/** Authoring user */
|
|
|
|
public function author()
|
|
|
|
{
|
|
|
|
return $this->belongsTo(User::class, 'author_id');
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Target revision */
|
|
|
|
public function revision()
|
|
|
|
{
|
|
|
|
return $this->belongsTo(Revision::class);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Target table (that this was submitted to) */
|
|
|
|
public function table()
|
|
|
|
{
|
|
|
|
return $this->belongsTo(Table::class);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function scopeUnmerged(Builder $query, Table $table)
|
|
|
|
{
|
|
|
|
return $query->whereRaw('
|
|
|
|
"id" NOT IN
|
|
|
|
(SELECT "proposal_id" FROM "revisions"
|
|
|
|
LEFT JOIN "table_revision_pivot"
|
|
|
|
ON "revisions"."id" = "table_revision_pivot"."revision_id"
|
|
|
|
WHERE "table_revision_pivot"."table_id" = ?)',
|
|
|
|
[
|
|
|
|
$table->getKey()
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getChangesetAttribute()
|
|
|
|
{
|
|
|
|
$changeset = Changeset::fromObject(fromJSON($this->attributes['changes'], true));
|
|
|
|
$changeset->revision = $this->revision;
|
|
|
|
$changeset->table = $this->getAttribute('table');
|
|
|
|
$changeset->note = $this->note;
|
|
|
|
|
|
|
|
return $changeset;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setChangesetAttribute($value)
|
|
|
|
{
|
|
|
|
if ($value instanceof Changeset) {
|
|
|
|
$this->attributes['changes'] = toJSON($value->toObject());
|
|
|
|
} else {
|
|
|
|
throw new NotApplicableException("Only a Changeset may be set to Proposal->changes");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new Proposal instance wrapping this changeset,
|
|
|
|
* owned by the currently logged in User. The created instance
|
|
|
|
* is NOT saved yet.
|
|
|
|
*
|
|
|
|
* @param Changeset $changeset - changeset to hydrate the proposal with
|
|
|
|
* @return Proposal
|
|
|
|
*/
|
|
|
|
public static function fromChangeset(Changeset $changeset)
|
|
|
|
{
|
|
|
|
if (!$changeset->hasAnyChanges()) {
|
|
|
|
throw new NotApplicableException('No changes to propose.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($changeset->note)) {
|
|
|
|
throw new NotApplicableException('Proposal note must be filled.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($changeset->table == null || !$changeset->table instanceof Table) {
|
|
|
|
throw new NotApplicableException('Table not assigned to Changeset');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($changeset->revision == null || !$changeset->revision instanceof Revision) {
|
|
|
|
throw new NotApplicableException('Revision not assigned to Changeset');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign unique row IDs to new rows (they use temporary negative IDs in a draft)
|
|
|
|
$changeset->renumberRows();
|
|
|
|
|
|
|
|
return new Proposal([
|
|
|
|
// relations
|
|
|
|
'table_id' => $changeset->table->getKey(),
|
|
|
|
'revision_id' => $changeset->revision->getKey(),
|
|
|
|
'author_id' => \user()->getKey(),
|
|
|
|
// the proposal info
|
|
|
|
'note' => $changeset->note,
|
|
|
|
'changeset' => $changeset, // this is without a note
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Accept the proposed changes: create a new revision based on the parent revision
|
|
|
|
* and changes described in this Proposal, and link it to the table.
|
|
|
|
*/
|
|
|
|
public function createRevision()
|
|
|
|
{
|
|
|
|
DB::transaction(function () {
|
|
|
|
$changeset = $this->changeset;
|
|
|
|
|
|
|
|
$columns = $changeset->fetchAndTransformColumns();
|
|
|
|
|
|
|
|
$newRevision = new Revision([
|
|
|
|
'ancestor_id' => $changeset->revision->getKey(),
|
|
|
|
'proposal_id' => $this->getKey(),
|
|
|
|
'note' => $changeset->note,
|
|
|
|
'row_count' => 0, // Will be set later when we are sure about the row count
|
|
|
|
'columns' => array_map(function(Column $c) {
|
|
|
|
return $c->toArray(false);
|
|
|
|
}, $columns),
|
|
|
|
]);
|
|
|
|
|
|
|
|
$newRevision->save(); // this gives it an ID, needed to associate rows
|
|
|
|
|
|
|
|
// --- Copy over rows that are left unchanged ---
|
|
|
|
// ...this directly works with the pivot
|
|
|
|
$removedRowIds = (array)($changeset->removedRows ?? []);
|
|
|
|
$changedRowIds = array_keys((array)($changeset->rowUpdates ?? []));
|
|
|
|
$excludedGRIDs = array_merge($removedRowIds, $changedRowIds);
|
|
|
|
|
|
|
|
$excluded_ids = [];
|
|
|
|
|
|
|
|
if ($excludedGRIDs) {
|
|
|
|
$questionmarks = str_repeat('?,', count($excludedGRIDs) - 1) . '?';
|
|
|
|
$excluded_ids = $changeset->revision->rows()->whereRaw("data->'_id' IN ($questionmarks)", $excludedGRIDs)
|
|
|
|
->get(['id'])->pluck('id')->toArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
$query = '
|
|
|
|
INSERT INTO revision_row_pivot
|
|
|
|
SELECT ? as revision_id, row_id FROM revision_row_pivot
|
|
|
|
WHERE revision_id = ?';
|
|
|
|
$subs = [
|
|
|
|
$newRevision->getKey(),
|
|
|
|
$changeset->revision->getKey()
|
|
|
|
];
|
|
|
|
if ($excluded_ids) {
|
|
|
|
$questionmarks = str_repeat('?,', count($excluded_ids) - 1) . '?';
|
|
|
|
$query .= ' AND row_id NOT IN (' . $questionmarks . ')';
|
|
|
|
$subs = array_merge($subs, $excluded_ids);
|
|
|
|
}
|
|
|
|
|
|
|
|
DB::statement($query, $subs);
|
|
|
|
|
|
|
|
// --- Insert modified rows ---
|
|
|
|
if ($changeset->rowUpdates) {
|
|
|
|
$ids = array_keys($changeset->rowUpdates);
|
|
|
|
$questionmarks = str_repeat('?,', count($ids) - 1) . '?';
|
|
|
|
|
|
|
|
$toChange = $changeset->revision->rows()->whereRaw("data->'_id' IN ($questionmarks)", $ids)->get();
|
|
|
|
|
|
|
|
$updateData = [];
|
|
|
|
foreach ($toChange as $row) {
|
|
|
|
$updateData[] = new Row([
|
|
|
|
'data' => $changeset->transformRow($row->data, false),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
$newRevision->rows()->saveMany($updateData);
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- Insert new rows ---
|
|
|
|
if ($changeset->newRows) {
|
|
|
|
$newRowData = [];
|
|
|
|
foreach ($changeset->newRows as $newRow) {
|
|
|
|
$newRowData[] = new Row(['data' => $newRow]);
|
|
|
|
}
|
|
|
|
$newRevision->rows()->saveMany($newRowData);
|
|
|
|
}
|
|
|
|
|
|
|
|
$newRevision->update(['row_count' => $newRevision->rows()->count()]);
|
|
|
|
|
|
|
|
// --- Attach this revision to the table ---
|
|
|
|
$changeset->table->revisions()->save($newRevision);
|
|
|
|
$changeset->table->update(['revision_id' => $newRevision->getKey()]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|