Merge branch 'staging' into 'master'
FIxed: Allow receipts with no transactions. See merge request s3lph/matemat!54
This commit is contained in:
commit
49856473b9
6 changed files with 159 additions and 11 deletions
|
@ -647,14 +647,14 @@ class MatematDatabase(object):
|
|||
else:
|
||||
t = Transaction(ta_id, user, value, old_balance, datetime.fromtimestamp(date))
|
||||
transactions.append(t)
|
||||
if write and len(transactions) > 0:
|
||||
if write:
|
||||
cursor.execute('''
|
||||
INSERT INTO receipts (user_id, first_ta_id, last_ta_id)
|
||||
VALUES (:user_id, :first_ta, :last_ta)
|
||||
''', {
|
||||
'user_id': user.id,
|
||||
'first_ta': transactions[0].id,
|
||||
'last_ta': transactions[-1].id
|
||||
'first_ta': transactions[0].id if len(transactions) != 0 else None,
|
||||
'last_ta': transactions[-1].id if len(transactions) != 0 else None
|
||||
})
|
||||
cursor.execute('''SELECT last_insert_rowid()''')
|
||||
receipt_id: int = int(cursor.fetchone()[0])
|
||||
|
|
|
@ -201,3 +201,41 @@ def migrate_schema_2_to_3(c: sqlite3.Cursor):
|
|||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
|
||||
def migrate_schema_3_to_4(c: sqlite3.Cursor):
|
||||
# Change receipts schema to allow null for transaction IDs
|
||||
c.execute('''
|
||||
CREATE TEMPORARY TABLE receipts_temp (
|
||||
receipt_id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
first_ta_id INTEGER DEFAULT NULL,
|
||||
last_ta_id INTEGER DEFAULT NULL,
|
||||
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
''')
|
||||
c.execute('INSERT INTO receipts_temp SELECT * FROM receipts')
|
||||
c.execute('DROP TABLE receipts')
|
||||
c.execute('''
|
||||
CREATE TABLE receipts (
|
||||
receipt_id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
first_ta_id INTEGER DEFAULT NULL,
|
||||
last_ta_id INTEGER DEFAULT NULL,
|
||||
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
''')
|
||||
c.execute('INSERT INTO receipts SELECT * FROM receipts_temp')
|
||||
c.execute('DROP TABLE receipts_temp')
|
||||
|
|
|
@ -178,3 +178,80 @@ SCHEMAS[3] = [
|
|||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
''']
|
||||
|
||||
SCHEMAS[4] = [
|
||||
'''
|
||||
CREATE TABLE users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT DEFAULT NULL,
|
||||
password TEXT NOT NULL,
|
||||
touchkey TEXT DEFAULT NULL,
|
||||
is_admin INTEGER(1) NOT NULL DEFAULT 0,
|
||||
is_member INTEGER(1) NOT NULL DEFAULT 1,
|
||||
balance INTEGER(8) NOT NULL DEFAULT 0,
|
||||
lastchange INTEGER(8) NOT NULL DEFAULT 0,
|
||||
receipt_pref INTEGER(1) NOT NULL DEFAULT 0,
|
||||
created INTEGER(8) NOT NULL DEFAULT 0
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE products (
|
||||
product_id INTEGER PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
stock INTEGER(8) NOT NULL DEFAULT 0,
|
||||
price_member INTEGER(8) NOT NULL,
|
||||
price_non_member INTEGER(8) NOT NULL
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE transactions ( -- "superclass" of the following 3 tables
|
||||
ta_id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER DEFAULT NULL,
|
||||
value INTEGER(8) NOT NULL,
|
||||
old_balance INTEGER(8) NOT NULL,
|
||||
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE consumptions ( -- transactions involving buying a product
|
||||
ta_id INTEGER PRIMARY KEY,
|
||||
product TEXT NOT NULL,
|
||||
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE deposits ( -- transactions involving depositing cash
|
||||
ta_id INTEGER PRIMARY KEY,
|
||||
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE modifications ( -- transactions involving balance modification by an admin
|
||||
ta_id INTEGER NOT NULL,
|
||||
agent TEXT NOT NULL,
|
||||
reason TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (ta_id),
|
||||
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE receipts ( -- receipts sent to the users
|
||||
receipt_id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
first_ta_id INTEGER DEFAULT NULL,
|
||||
last_ta_id INTEGER DEFAULT NULL,
|
||||
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
''']
|
||||
|
|
|
@ -506,14 +506,14 @@ class DatabaseTest(unittest.TestCase):
|
|||
db.increment_consumption(user, product)
|
||||
db.deposit(user, 1337)
|
||||
receipt1: Receipt = db.create_receipt(user, write=True)
|
||||
# Attempt to create a receipt with zero transactions. Won't be written to DB.
|
||||
# Attempt to create a receipt with zero transactions. Will carry NULL transaction IDs
|
||||
receipt2: Receipt = db.create_receipt(user, write=True)
|
||||
self.assertEqual(-1, receipt2.id)
|
||||
self.assertNotEqual(-1, receipt2.id)
|
||||
self.assertEqual(0, len(receipt2.transactions))
|
||||
|
||||
with db.transaction() as c:
|
||||
c.execute('SELECT COUNT(receipt_id) FROM receipts')
|
||||
self.assertEqual(1, c.fetchone()[0])
|
||||
self.assertEqual(2, c.fetchone()[0])
|
||||
|
||||
db.increment_consumption(user, product)
|
||||
db.change_user(user, agent=admin, balance=4200)
|
||||
|
@ -529,7 +529,7 @@ class DatabaseTest(unittest.TestCase):
|
|||
|
||||
with db.transaction() as c:
|
||||
c.execute('SELECT COUNT(receipt_id) FROM receipts')
|
||||
self.assertEqual(1, c.fetchone()[0])
|
||||
self.assertEqual(2, c.fetchone()[0])
|
||||
|
||||
self.assertEqual(user, receipt1.user)
|
||||
self.assertEqual(3, len(receipt1.transactions))
|
||||
|
|
|
@ -183,3 +183,36 @@ class TestMigrations(unittest.TestCase):
|
|||
self.assertEqual('Flora Power Mate', cursor.fetchone()[0])
|
||||
cursor.execute('''SELECT product FROM consumptions WHERE ta_id = 5''')
|
||||
self.assertEqual('<unknown>', cursor.fetchone()[0])
|
||||
|
||||
def test_upgrade_3_to_4(self):
|
||||
# Setup test db with example entries to test schema change
|
||||
self._initialize_db(3)
|
||||
cursor: sqlite3.Cursor = self.db._sqlite_db.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO users VALUES
|
||||
(1, 'testadmin', 'a@b.c', '$2a$10$herebehashes', NULL, 1, 1, 1337, 0, 0, 0)
|
||||
''')
|
||||
cursor.execute('''
|
||||
INSERT INTO products VALUES
|
||||
(1, 'Club Mate', 42, 200, 250)
|
||||
''')
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions VALUES (1, 1, 4200, 0, 1000)
|
||||
''')
|
||||
cursor.execute('''
|
||||
INSERT INTO receipts VALUES (1, 1, 1, 1, 1337)
|
||||
''')
|
||||
cursor.execute('PRAGMA user_version = 3')
|
||||
|
||||
# Kick off the migration
|
||||
schema_version = self.db.SCHEMA_VERSION
|
||||
self.db.SCHEMA_VERSION = 4
|
||||
self.db._setup()
|
||||
self.db.SCHEMA_VERSION = schema_version
|
||||
|
||||
# Make sure entries from the receipts table are preserved
|
||||
cursor.execute('''SELECT COUNT(receipt_id) FROM receipts''')
|
||||
self.assertEqual(1, cursor.fetchone()[0])
|
||||
|
||||
# Make sure transaction IDs can be set to NULL
|
||||
cursor.execute('UPDATE receipts SET first_ta_id = NULL, last_ta_id = NULL')
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, Optional
|
||||
|
||||
import sqlite3
|
||||
|
||||
from matemat.exceptions import DatabaseConsistencyError
|
||||
from matemat.db.schemas import SCHEMAS
|
||||
from matemat.db.migrations import migrate_schema_1_to_2, migrate_schema_2_to_3
|
||||
from matemat.db.migrations import *
|
||||
|
||||
|
||||
class DatabaseTransaction(object):
|
||||
|
@ -43,7 +41,7 @@ class DatabaseTransaction(object):
|
|||
|
||||
class DatabaseWrapper(object):
|
||||
|
||||
SCHEMA_VERSION = 3
|
||||
SCHEMA_VERSION = 4
|
||||
|
||||
def __init__(self, filename: str) -> None:
|
||||
self._filename: str = filename
|
||||
|
@ -86,6 +84,8 @@ class DatabaseWrapper(object):
|
|||
migrate_schema_1_to_2(c)
|
||||
if from_version <= 2 and to_version >= 3:
|
||||
migrate_schema_2_to_3(c)
|
||||
if from_version <= 3 and to_version >= 4:
|
||||
migrate_schema_3_to_4(c)
|
||||
|
||||
def connect(self) -> None:
|
||||
if self.is_connected():
|
||||
|
|
Loading…
Reference in a new issue