185 lines
6.2 KiB
Python
185 lines
6.2 KiB
Python
import enum
|
|
|
|
from app.extensions import db
|
|
from app.models.base import TimestampMixin
|
|
|
|
|
|
invoice_tags = db.Table(
|
|
'invoice_tags',
|
|
db.Column('invoice_id', db.Integer, db.ForeignKey('invoice.id'), primary_key=True),
|
|
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
|
|
)
|
|
|
|
|
|
class InvoiceType(enum.Enum):
|
|
PURCHASE = 'purchase'
|
|
SALE = 'sale'
|
|
CORRECTION = 'correction'
|
|
|
|
|
|
class InvoiceStatus(enum.Enum):
|
|
NEW = 'new'
|
|
READ = 'read'
|
|
ACCOUNTED = 'accounted'
|
|
SENT = 'sent'
|
|
ARCHIVED = 'archived'
|
|
NEEDS_ATTENTION = 'needs_attention'
|
|
ERROR = 'error'
|
|
|
|
|
|
|
|
INVOICE_TYPE_LABELS = {
|
|
InvoiceType.PURCHASE: 'Zakupowa',
|
|
InvoiceType.SALE: 'Sprzedażowa',
|
|
InvoiceType.CORRECTION: 'Korekta',
|
|
}
|
|
|
|
INVOICE_STATUS_LABELS = {
|
|
InvoiceStatus.NEW: 'Nowa',
|
|
InvoiceStatus.READ: 'Odczytana',
|
|
InvoiceStatus.ACCOUNTED: 'Zaksięgowana',
|
|
InvoiceStatus.SENT: 'Wysłana',
|
|
InvoiceStatus.ARCHIVED: 'Archiwalna',
|
|
InvoiceStatus.NEEDS_ATTENTION: 'Do księgowania',
|
|
InvoiceStatus.ERROR: 'Błąd',
|
|
}
|
|
|
|
ISSUED_STATUS_LABELS = {
|
|
'draft': 'Robocza',
|
|
'pending': 'Oczekuje wysyłki',
|
|
'issued': 'Wysłana do KSeF',
|
|
'received': 'Odebrana',
|
|
'read': 'Odczytana',
|
|
'accounted': 'Zaksięgowana',
|
|
'queued': 'W kolejce',
|
|
'error': 'Błąd',
|
|
'sent': 'Wysłana',
|
|
'needs_attention': 'Do księgowania',
|
|
'issued_mock': 'Wysłana testowo',
|
|
}
|
|
|
|
|
|
class Invoice(TimestampMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), index=True)
|
|
ksef_number = db.Column(db.String(128), nullable=False, index=True)
|
|
invoice_number = db.Column(db.String(128), nullable=False, index=True)
|
|
|
|
contractor_name = db.Column(db.String(255), nullable=False, index=True)
|
|
contractor_nip = db.Column(db.String(32), index=True)
|
|
contractor_regon = db.Column(db.String(32), index=True)
|
|
contractor_address = db.Column(db.String(512))
|
|
|
|
issue_date = db.Column(db.Date, nullable=False, index=True)
|
|
received_date = db.Column(db.Date, index=True)
|
|
fetched_at = db.Column(db.DateTime, index=True)
|
|
|
|
net_amount = db.Column(db.Numeric(12, 2), nullable=False, default=0)
|
|
vat_amount = db.Column(db.Numeric(12, 2), nullable=False, default=0)
|
|
gross_amount = db.Column(db.Numeric(12, 2), nullable=False, default=0)
|
|
split_payment = db.Column(db.Boolean, default=False, nullable=False)
|
|
currency = db.Column(db.String(8), default='PLN')
|
|
seller_bank_account = db.Column(db.String(64), default='')
|
|
|
|
invoice_type = db.Column(db.Enum(InvoiceType), nullable=False, default=InvoiceType.PURCHASE)
|
|
status = db.Column(db.Enum(InvoiceStatus), nullable=False, default=InvoiceStatus.NEW)
|
|
|
|
xml_path = db.Column(db.String(512))
|
|
pdf_path = db.Column(db.String(512))
|
|
html_preview = db.Column(db.Text)
|
|
internal_note = db.Column(db.Text)
|
|
source_hash = db.Column(db.String(128))
|
|
read_at = db.Column(db.DateTime)
|
|
last_synced_at = db.Column(db.DateTime)
|
|
|
|
external_metadata = db.Column(db.JSON, default=dict)
|
|
|
|
is_unread = db.Column(db.Boolean, default=True, nullable=False)
|
|
pinned = db.Column(db.Boolean, default=False, nullable=False)
|
|
queue_accounting = db.Column(db.Boolean, default=False, nullable=False)
|
|
source = db.Column(db.String(32), default='ksef', nullable=False)
|
|
|
|
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), index=True)
|
|
|
|
issued_to_ksef_at = db.Column(db.DateTime)
|
|
issued_status = db.Column(db.String(32), default='received', nullable=False)
|
|
|
|
tags = db.relationship(
|
|
'Tag',
|
|
secondary=invoice_tags,
|
|
lazy='joined',
|
|
backref=db.backref('invoices', lazy='dynamic'),
|
|
)
|
|
sync_events = db.relationship(
|
|
'SyncEvent',
|
|
backref='invoice',
|
|
lazy='dynamic',
|
|
cascade='all, delete-orphan',
|
|
)
|
|
mail_deliveries = db.relationship(
|
|
'MailDelivery',
|
|
backref='invoice',
|
|
lazy='dynamic',
|
|
cascade='all, delete-orphan',
|
|
)
|
|
notifications = db.relationship(
|
|
'NotificationLog',
|
|
backref='invoice',
|
|
lazy='dynamic',
|
|
cascade='all, delete-orphan',
|
|
)
|
|
company = db.relationship('Company', backref=db.backref('invoices', lazy='dynamic'))
|
|
customer = db.relationship('Customer', backref=db.backref('invoices', lazy='dynamic'))
|
|
|
|
__table_args__ = (
|
|
db.UniqueConstraint('company_id', 'ksef_number', name='uq_invoice_company_ksef'),
|
|
)
|
|
|
|
@property
|
|
def month_key(self):
|
|
return f'{self.issue_date.year}-{self.issue_date.month:02d}'
|
|
|
|
@property
|
|
def invoice_type_label(self):
|
|
return INVOICE_TYPE_LABELS.get(self.invoice_type, getattr(self.invoice_type, 'value', self.invoice_type))
|
|
|
|
@property
|
|
def status_label(self):
|
|
return INVOICE_STATUS_LABELS.get(self.status, getattr(self.status, 'value', self.status))
|
|
|
|
@property
|
|
def issued_status_label(self):
|
|
return ISSUED_STATUS_LABELS.get(self.issued_status, self.issued_status)
|
|
|
|
|
|
class Tag(TimestampMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(64), unique=True, nullable=False)
|
|
color = db.Column(db.String(32), default='secondary')
|
|
|
|
|
|
class SyncEvent(TimestampMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
invoice_id = db.Column(db.Integer, db.ForeignKey('invoice.id'), nullable=False)
|
|
status = db.Column(db.String(32), nullable=False)
|
|
message = db.Column(db.Text)
|
|
source = db.Column(db.String(32), default='ksef')
|
|
|
|
|
|
class MailDelivery(TimestampMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
invoice_id = db.Column(db.Integer, db.ForeignKey('invoice.id'), nullable=False)
|
|
recipient = db.Column(db.String(255), nullable=False)
|
|
status = db.Column(db.String(32), default='queued')
|
|
subject = db.Column(db.String(255))
|
|
error_message = db.Column(db.Text)
|
|
sent_at = db.Column(db.DateTime)
|
|
|
|
|
|
class NotificationLog(TimestampMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
invoice_id = db.Column(db.Integer, db.ForeignKey('invoice.id'))
|
|
channel = db.Column(db.String(32), nullable=False)
|
|
status = db.Column(db.String(32), default='queued')
|
|
message = db.Column(db.Text)
|
|
sent_at = db.Column(db.DateTime) |