class Entity(db.Model, TimestampMixin): """ Base class for entity submodels. An entity is a model that has two names, one required and one optional, and a series of address, phone, emails and extra fields. Models to be suitable to this description are for example: User, Employee, Supplier, Customer, Contact, Branch, etc. """ __tablename__ = 'entity' id = db.Column(db.Integer, primary_key=True) _name_1 = db.Column('name_1', db.Unicode, nullable=False) _name_2 = db.Column('name_2', db.Unicode) entity_type = db.Column(db.Unicode(50)) notes = db.Column(db.UnicodeText) __mapper_args__ = {'polymorphic_on': entity_type} @property def full_name(self): ln = " {0}".format(self._name_2) if self._name_2 else "" return "{0}{1}".format(self._name_1, ln) def __repr__(self): return "<{0} '{1}'>".format(self.__class__.__name__, self.full_name)
class FiscalData(db.Model): __tablename__ = 'fiscal_data' FISCAL_CONSUMIDOR_FINAL = 'CONSUMIDOR FINAL' FISCAL_RESPONSABLE_INSCRIPTO = 'RESPONSABLE INSCRIPTO' FISCAL_EXCENTO = 'EXCENTO' FISCAL_MONOTRIBUTO = 'MONOTRIBUTO' _fiscal_types = { FISCAL_CONSUMIDOR_FINAL: 'Consumidor Final', FISCAL_RESPONSABLE_INSCRIPTO: 'Responsable Inscripto', FISCAL_EXCENTO: 'Excento', FISCAL_MONOTRIBUTO: 'Monotributo', } id = db.Column(db.Integer, primary_key=True) cuit = db.Column(db.Unicode(13)) fiscal_type = db.Column(db.Enum(*_fiscal_types.keys(), name='fiscal_type'), default=FISCAL_CONSUMIDOR_FINAL) iibb = db.Column(db.Unicode, nullable=True) @property def needs_cuit(self): return self.fiscal_type not in (self.FISCAL_CONSUMIDOR_FINAL, ) @property def type(self): return self._fiscal_types.get(self.fiscal_type) def __repr__(self): return "<FiscalData '{} {}' of '{}'>".format( self.type, self.cuit, self.entity.full_name, )
class Product(db.Model): __tablename__ = 'product' id = db.Column(db.Integer, primary_key=True) code = db.Column(db.Unicode(14), nullable=False, unique=True) description = db.Column(db.Unicode(40), nullable=False) brand = db.Column(db.UnicodeText) suppliers = association_proxy('suppliers_info', 'supplier')
class Email(RefEntityMixin, db.Model): "Model to store entity's email information" __tablename__ = 'email' id = db.Column(db.Integer, primary_key=True) email_type = db.Column(db.Unicode(50)) email = db.Column(db.Unicode(50), nullable=False) def __str__(self): retval = self.email_type + ': ' if self.email_type else '' retval += self.email return retval
class Payment(db.Model): __tablname__ = 'payment' id = db.Column(db.Integer, primary_key=True) amount = db.Column(db.Numeric(10, 2), nullable=False) date = db.Column(db.Date, nullable=False) notes = db.Column(db.UnicodeText) documents = association_proxy('document_payment', 'document') def add_documents(self, documents): for document in documents: document.add_payment(self)
class Phone(RefEntityMixin, db.Model): "Model to store entity's phone information" __tablename__ = 'phone' id = db.Column(db.Integer, primary_key=True) phone_type = db.Column(db.Unicode) number = db.Column(db.Unicode, nullable=False) def __str__(self): retval = self.phone_type + ': ' if self.phone_type else '' retval += self.number return retval
class TimestampMixin(object): created = db.Column(db.DateTime, default=datetime.now) modified = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) @staticmethod def stamp_modified(mapper, connection, target): if db.object_session(target).is_modified(target): target.modified = datetime.now() @classmethod def __declare_last__(cls): db.event.listen(cls, 'before_update', cls.stamp_modified)
class SupplierContact(db.Model): __tablename__ = 'supplier_contact' supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.supplier_id'), primary_key=True) contact_id = db.Column(db.Integer, db.ForeignKey('contact.contact_id'), primary_key=True) role = db.Column(db.Unicode) contact = db.relationship('Contact', backref='supplier_contacts', lazy='joined') def __init__(self, contact, role): self.contact = contact self.role = role
class DocumentPayment(db.Model): __tablename__ = 'document_payment' document_id = db.Column(db.Integer, db.ForeignKey('document.id'), primary_key=True) payment_id = db.Column(db.Integer, db.ForeignKey('payment.id'), primary_key=True) amount = db.Column(db.Numeric(10, 2), nullable=False) payment = db.relationship(Payment, lazy='joined', backref='document_payments') document = db.relationship('Document', lazy='joined', backref='document_payments') def __init__(self, payment, amount): self.payment = payment self.amount = amount
class Comment(db.Model, TimestampMixin): __tablename__ = 'comment' id = db.Column(db.Integer, primary_key=True) comment = db.Column(db.UnicodeText, nullable=False) created_by_id = db.Column(db.Integer, db.ForeignKey('user.user_id'), nullable=False) created_by = db.relationship( 'User', backref="comments_created", lazy='joined', primaryjoin='User.user_id==Comment.modified_by_id') modified_by_id = db.Column(db.Integer, db.ForeignKey('user.user_id'), nullable=False) modified_by = db.relationship( 'User', backref='comments_modified', lazy='joined', primaryjoin='User.user_id==Comment.modified_by_id') supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.supplier_id'), nullable=False) supplier = db.relationship('Supplier', backref='comments', lazy='joined')
class Contact(Entity): __tablename__ = 'contact' __mapper_args__ = {'polymorphic_identity': 'contact'} contact_id = db.Column(db.Integer, db.ForeignKey('entity.id'), primary_key=True) first_name = Entity._name_1 last_name = Entity._name_2 suppliers = association_proxy('supplier_contacts', 'supplier') @property def full_name(self): return u' '.join([self.first_name, self.last_name])
class ProductSupplierInfo(db.Model): __tablename__ = 'productsupplier_info' supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.supplier_id'), primary_key=True) supplier = db.relationship('Supplier', backref='products_info', lazy='joined') product_id = db.Column(db.Integer, db.ForeignKey('product.id'), primary_key=True) product = db.relationship('Product', backref='suppliers_info', lazy='joined') code = db.Column(db.Unicode(80)) description = db.Column(db.Unicode) base_cost = db.Column(db.Numeric(10, 2)) minimum_purchase = db.Column(db.Integer, default=1)
class PurchaseOrderItem(db.Model): __tablename__ = 'purchaseorder_item' id = db.Column(db.Integer, primary_key=True) sku = db.Column(db.Unicode(40)) description = db.Column(db.UnicodeText) quantity = db.Column(db.Integer, default=0) quantity_received = db.Column(db.Integer, default=0) quantity_returned = db.Column(db.Integer, default=0) order_id = db.Column(db.Integer, db.ForeignKey('purchaseorder.id'), nullable=False) order = db.relationship('PurchaseOrder', backref='items') @property def pending_quantity(self): return self.quantity - self.quantity_received
class Address(RefEntityMixin, db.Model): "Stores entity's addresses information" __tablename__ = 'address' id = db.Column(db.Integer, primary_key=True) street = db.Column(db.Unicode(64), nullable=False) streetnumber = db.Column(db.Unicode(64)) city = db.Column(db.Unicode(64)) province = db.Column(db.Unicode(32), nullable=False) zip_code = db.Column(db.Unicode(32)) address_type = db.Column(db.Unicode) def __str__(self): retval = self.street retval += " %s" % self.streetnumber if self.streetnumber else 'S/N' if self.city: retval += ", %s" % self.city retval += ", %s" % self.province if self.postal_code: retval += " (%s)" % self.postal_code return retval
def entity_id(cls): return db.Column('entity_id', db.Integer, db.ForeignKey('entity.id'), nullable=False)
class Supplier(Entity): __tablename__ = 'supplier' __mapper_args__ = {'polymorphic_identity': 'supplier'} supplier_id = db.Column(db.Integer, db.ForeignKey('entity.id'), primary_key=True) rz = Entity._name_1 name = Entity._name_2 web = db.Column(db.Unicode, default=None) fiscal_data_id = db.Column(db.Integer, db.ForeignKey('fiscal_data.id')) fiscal_data = db.relationship(FiscalData, backref=db.backref('entity', uselist=False)) payment_term = db.Column(db.Integer) # in days leap_time = db.Column(db.Integer) # in days delivery_included = db.Column(db.Boolean) supplier_contacts = db.relationship('SupplierContact', cascade='all, delete-orphan', backref='supplier') contacts = association_proxy('supplier_contacts', 'contact') debt = db.Column(db.Numeric(10, 2)) expired = db.Column(db.Numeric(10, 2)) expiration_date = db.Column(db.DateTime, default=None) #: 'bank_accounts' attribute added by BankAccount.supplier relation #: 'orders' attribute added by PurchaseOrder.supplier relation #: 'documents' attribute added by Document.supplier relation #: Inherited from Entity #: - address (collection) #: - email (collection) #: - phone (collection) #: - extra field (collection) # products = association_proxy('products_info', 'product') def add_contact(self, contact, role): self.supplier_contacts.append(SupplierContact(contact, role)) # def add_product(self, product, **kwargs): # self.products_info.append(ProductSupplierInfo(product=product, **kwargs)) @property def full_name(self): n = " ({0})".format(self.name) if self.name else '' return "{0}{1}".format(self.rz, n) def _update_expiration_date(self): doc = self.documents\ .filter(Document.doc_status.in_([Document.STATUS_PENDING, Document.STATUS_EXPIRED]))\ .order_by(Document.expiration_date.asc())\ .first() if doc: self.expiration_date = doc.expiration_date else: self.expiration_date = None
class PurchaseOrder(db.Model, TimestampMixin): __tablename__ = 'purchaseorder' STATUS_CANCELLED = 'STATUS_CANCELLED' STATUS_QUOTING = 'STATUS_QUOTING' STATUS_PENDING = 'STATUS_PENDING' STATUS_PARTIAL = 'STATUS_PARTIAL' STATUS_CONFIRMED = 'STATUS_CONFIRMED' STATUS_COMPLETED = 'STATUS_COMPLETED' STATUS_DRAFT = 'STATUS_DRAFT' _po_status = { STATUS_CANCELLED: 'Cancelada', STATUS_QUOTING: 'Valorizando', STATUS_PENDING: 'Pendiente', STATUS_PARTIAL: 'Parcial', STATUS_CONFIRMED: 'Confirmada', STATUS_COMPLETED: 'Completa', STATUS_DRAFT: 'Borrador', } METHOD_EMAIL = 'METHOD_EMAIL' METHOD_FAX = 'METHOD_FAX' METHOD_PHONE = 'METHOD_PHONE' METHOD_PERSONALLY = 'METHOD_PERSONALLY' _po_method = { METHOD_EMAIL: 'Correo Electrónico', METHOD_FAX: 'Fax', METHOD_PHONE: 'Telefónico', METHOD_PERSONALLY: 'Personalmente', } id = db.Column(db.Integer, primary_key=True) point_sale = db.Column(db.Integer, nullable=False) number = db.Column(db.Integer, nullable=False) po_status = db.Column(db.Enum(*_po_status.keys(), name='po_status'), default=STATUS_DRAFT) po_method = db.Column(db.Enum(*_po_method.keys(), name='po_method'), default=None) notes = db.Column(db.UnicodeText) open_date = db.Column(db.Date) confirm_date = db.Column(db.Date) receival_date = db.Column(db.Date) supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.supplier_id'), nullable=False) supplier = db.relationship('Supplier', backref="orders") @property def status(self): return self._po_status[self.po_status] @property def method(self): return self._po_method[self.po_method] @property def full_desc(self): if self.point_sale == 0: return "Pedido 0-%04d-%08d" % (self.supplier_id, self.number) return "Pedido %04d-%08d" % (self.point_sale, self.number)
class Document(db.Model, TimestampMixin): __tablename__ = 'document' TYPE_FACTURA_A = 'TYPE_FACTURA_A' TYPE_NOTA_CREDITO_A = 'TYPE_NOTA_CREDITO_A' TYPE_PRESUPUESTO = 'TYPE_PRESUPUESTO' _doc_type = { TYPE_FACTURA_A: 'Factura A', TYPE_NOTA_CREDITO_A: 'Nota de Crédito', TYPE_PRESUPUESTO: 'Presupuesto', } STATUS_PENDING = 'STATUS_PENDING' STATUS_EXPIRED = 'STATUS_EXPIRED' STATUS_PAID = 'STATUS_PAID' _doc_status = { STATUS_PENDING: 'Pendiente', STATUS_EXPIRED: 'Vencida', STATUS_PAID: 'Pagada', } id = db.Column(db.Integer, primary_key=True) doc_type = db.Column(db.Enum(*_doc_type.keys(), name='doc_type'), default=TYPE_FACTURA_A) point_sale = db.Column(db.Integer, nullable=False) number = db.Column(db.Integer, nullable=False) total = db.Column(db.Numeric(10, 2), nullable=False) doc_status = db.Column(db.Enum(*_doc_status.keys(), name='doc_status'), default=STATUS_PENDING) issue_date = db.Column(db.Date) expiration_date = db.Column(db.Date) notes = db.Column(db.UnicodeText) supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.supplier_id'), nullable=False) supplier = db.relationship('Supplier', backref=db.backref('documents', order_by='Document.issue_date.asc()', lazy='dynamic')) #: document_payments added by DocumentPayment.document relationship def add_payment(self, payment, amount=None): if amount is None: amount = min([payment.amount, self.balance]) self.document_payments.append(DocumentPayment(payment, amount)) @property def type(self): return self._doc_type.get(self.doc_type) @property def status(self): return self._doc_status.get(self.doc_status) @property def full_desc(self): return u"%s %04d-%08d" % (self.type, self.point_sale, self.number) @property def cancelled_date(self): if self.state in (self.STATUS_PENDING, self.STATUS_EXPIRED): return None return Payment.query.join('document_payment')\ .filter(DocumentPayment.document==self)\ .order_by(Payment.date.desc())[0].date @property def balance(self): paid = db.session.query(db.func.sum(DocumentPayment.amount))\ .filter(DocumentPayment.document==self).scalar()\ or Decimal(0) return self.total - paid def __repr__(self): return "<Document '{}' of '{}' ({})>".format(self.full_desc, self.supplier.rz, self.status)