import { Observable , Subject , Subscription } from 'rxjs' ; import { RecordModel } from '../models/record-model' ; import { ListModel } from '../models/list-model' ; import { RelatedRecords } from './related-records' ; import { Message , instanceOfMessage } from '../message' ; import { Backend } from '../base-backend' ; import { BaseField , BaseFieldEvent , BaseVirtualField , BaseDbField } from '../fields/base' ; import { Labels } from '../labels' ; import { Query , InsertQuery , UpdateQuery , DeleteQuery , SaveQuery } from '../query/query' ; import { QuerySet } from '../query/queryset' ; import { Q , Condition } from '../query/q' ; import { ListRow , ListRowDirector } from './list-row' ; import { FieldChangedEvent } from './data-item' ; export interface RecordLookupData { condition : Condition ; model : RecordModel ; } export interface RecordCommonErrorMessage { message : string ; } export interface RecordFieldErrorMessage { message : string ; } export interface RecordVirtualFieldErrorMessage { message ? : string ; fieldErrorData ? : any ; } export type RecordErrorMessages = { common ? : RecordCommonErrorMessage [ ] ; field ? : { [ fieldName ] : RecordFieldErrorMessage [ ] ; } ; virtualField ? : { [ fieldName ] : RecordVirtualFieldErrorMessage ; } ; } ; export function instanceOfRecordErrorMessage ( object ) { return object && typeof object == "object" && ( 'common' in object || 'field' in object || 'virtualField' in object ) ; } export class RecordSaveError extends Error { messages : RecordErrorMessages ; constructor ( messages ) { super ( "Record can't be saved." ) ; this . messages = messages ; } getMessages ( ) { return this . messages ; } } export type RecordRawData = { [ name ] : string | number | Date | Object ; } ; export interface BaseVirtualFieldStateData { __state__ : { } ; } export interface RecordStateData extends BaseVirtualFieldStateData { __state__ : { isNew : boolean ; isChanged : boolean ; isFull : boolean ; uid : string ; modifiedFields : string [ ] ; lookupData ? : { [ name ] : string | number | Date | Object ; } ; } ; [ name ] : string | number | Date | Object | BaseVirtualFieldStateData ; } ; export function instanceOfRecordStateData ( object ) { return '__state__' in object && 'isNew' in object . __state__ && 'isChanged' in object . __state__ ; } export type RecordFieldValue = any ; export type RecordProcessedData = { [ name ] : RecordFieldValue ; } ; export class RelFieldChangedEvent { constructor ( public name , public value , public event ) { } } export type RecordChangedEvent = FieldChangedEvent | RelFieldChangedEvent ; var recordCounter = 0 ; export class RecordDirector extends ListRowDirector { host : Record ; backend : Backend ; loaded = new Subject < Record > ( ) ; changed = new Subject < RecordChangedEvent > ( ) ; messagesChanged = new Subject < Record > ( ) ; messages : RecordCommonErrorMessage [ ] = [ ] ; fieldMessages = new Map < string , RecordFieldErrorMessage [ ] > ( ) ; uid : string ; labels : Labels = new Labels ( ) ; isNew : boolean ; isChildrenChanged : boolean = false ; isFull : boolean = false ; isDeleted : boolean = false ; modifiedFields : string [ ] = [ ] ; lookupData : RecordLookupData ; fieldData : Map < string , any > = new Map ( ) ; private initData : RecordRawData = { } ; destroyed = false ; constructor ( public model , data ? , isNew ? ) { super ( data , model ) ; this . isNew = ! ! isNew ; this . generateUid ( ) ; this . backend = model . backend ; for ( let serviceFieldName of this . model . serviceFields ) { Object . defineProperty ( this , serviceFieldName , { get : ( ) => { return this . data [ serviceFieldName ] ; } , set : function ( newValue ) { this . _data [ serviceFieldName ] = newValue ; } } ) ; } } generateUid ( ) { this . uid = Date . now ( ) + '_' + recordCounter ++ ; } init ( data ? ) { this . isChildrenChanged = false ; this . modifiedFields = [ ] ; this . initData = { } ; this . isFull = true ; this . lookupData = undefined ; var virtualDataTotSet = { } ; for ( let field of this . model . getVirtualFields ( ) ) { var initValue = undefined ; if ( field . name in data ) initValue = field . fromRAWtoInternal ( data [ field . name ] , this . host ) ; else if ( this . host [ field . name ] === undefined ) initValue = field . constructDefaultValue ( this . host ) ; else continue ; virtualDataTotSet [ field . name ] = initValue ; } this . setValues ( virtualDataTotSet , false ) ; var realDataToSet = { } ; for ( let field of this . model . getDbFields ( ) ) { var initValue = undefined ; if ( ! data || ! ( field . name in data ) ) { if ( this . host [ field . name ] === undefined ) { if ( this . isNew ) initValue = field . constructDefaultValue ( this . host ) ; else this . isFull = false ; } else { continue ; } } else { initValue = data [ field . name ] !== undefined ? field . fromRAWtoInternal ( data [ field . name ] , this . host ) : undefined ; this . initData [ field . name ] = data [ field . name ] ; } realDataToSet [ field . name ] = initValue ; } this . setValues ( realDataToSet , false ) ; for ( let serviceFieldName of this . model . serviceFields ) { if ( data [ serviceFieldName ] ) { this . data [ serviceFieldName ] = data [ serviceFieldName ] ; } } } setValues ( values , emitChange = true ) { var changed = [ ] ; for ( let fieldName in values ) { var value = values [ fieldName ] ; var field = this . getField ( fieldName ) ; var oldValue = this . data [ fieldName ] ; if ( value === oldValue ) continue ; if ( ! field . checkValue ( value ) ) { throw new Error ( 'Value for field ' + fieldName + ' not acceptable.' ) ; } if ( emitChange && ! ( field instanceof BaseVirtualField ) && this . modifiedFields . indexOf ( fieldName ) == - 1 ) { this . modifiedFields . push ( fieldName ) ; } field . destroyValue ( this . host ) ; field . prepareValue ( this . host , oldValue , value ) ; changed . push ( [ field , value , oldValue ] ) ; } super . setValues ( values , emitChange ) ; for ( let field of this . model . getVirtualFields ( ) ) { field . dataChanged ( Object . keys ( values ) , this . host , emitChange ) ; } if ( emitChange || this . isNew ) { for ( let ch of changed ) { this . onValueChanged ( ch [ 0 ] , ch [ 1 ] , ch [ 2 ] ) ; } } } get isChanged ( ) : boolean { return this . modifiedFields . length !== 0 ; } get hasAnyChanges ( ) : boolean { return this . isChanged || this . isChildrenChanged ; } removeChangedStatus ( ) { this . modifiedFields = [ ] ; this . isChildrenChanged = false ; } onValueChanged ( field , newValue , oldValue ) { } getDataObservable : Observable < Record > ; getData ( reload = false , ... fields ) { if ( ! reload && ( this . isFull || this . isChanged || this . isNew ) ) { return Observable . of ( this . host ) ; } else if ( ! this . getDataObservable ) { var query = new Query ( this . backend . getListModel ( this . model . listName ) ) ; query . add_q ( new Q ( this . getLookupData ( ) . condition ) ) ; this . getDataObservable = this . backend . getRow ( query , fields ) . map ( ( rawRecord ) => { this . isNew = false ; this . setMessages ( { } ) ; this . init ( rawRecord ) ; this . loaded . next ( this . host ) ; this . getDataObservable = undefined ; return this . host ; } ) . catch ( ( error , caught ) => { this . getDataObservable = undefined ; throw error ; } ) ; } return this . getDataObservable ; } getLookupData ( ) { if ( this . isNew ) { throw new Error ( 'Can\' generate lookup data for not saved record.' ) ; } if ( this . lookupData ) return this . lookupData ; var result = { } ; for ( let field of this . model . getLookupFields ( ) ) { if ( ! ( field instanceof BaseDbField ) ) continue ; if ( this . initData [ field . name ] !== undefined ) result [ field . name ] = this . initData [ field . name ] ; } return { condition : result , model : this . model } ; } setLookupData ( lookupData ) { this . lookupData = lookupData ; } getDeleteQuery ( ) { var list = this . backend . getListModel ( this . model . listName ) ; var query = new DeleteQuery ( list ) ; let condition = { } ; for ( let field of this . model . getLookupFields ( ) ) { condition [ field . name ] = this . initData [ field . name ] ; } query . add_q ( new Q ( condition ) ) ; query . setInitiator ( this . host ) ; return query ; } getSaveQuery ( skipFields = [ ] ) { var listModel = this . backend . getListModel ( this . model . listName ) ; var query ; if ( this . isNew ) { query = new InsertQuery ( this . model ) ; var fields = [ ] ; for ( let field of this . model . getDbFields ( ) ) { fields . push ( field ) ; } query . insertValues ( fields , [ this . host ] ) ; } else { query = new UpdateQuery ( listModel ) ; let condition = { } ; for ( let field of this . model . getLookupFields ( ) ) { condition [ field . name ] = this . initData [ field . name ] ; } query . add_q ( new Q ( condition ) ) ; let modifiedFields = { } ; for ( let fieldName of this . modifiedFields ) { modifiedFields [ fieldName ] = this . host [ fieldName ] ; } query . addUpdateValues ( modifiedFields ) ; query . setInitiator ( this . host ) ; } for ( let virtualField of this . model . getVirtualFields ( ) ) { if ( skipFields . indexOf ( virtualField . name ) !== - 1 ) continue ; var virtualFieldSaveQueries = virtualField . getSaveQueries ( this . host [ virtualField . name ] ) ; if ( virtualFieldSaveQueries ) query . addChildQuery ( virtualField , virtualFieldSaveQueries ) ; } return query ; } save ( commit = true , init = true ) { var query = this . getSaveQuery ( ) ; return this . model . backend . saveRecord ( query , this . uid , commit , init ) . map ( recordRawData => { this . setMessages ( { } ) ; this . isNew = false ; if ( init ) { this . init ( recordRawData ) ; this . loaded . next ( this . host ) ; } return this . host ; } ) . catch ( ( error , caught ) => { if ( error instanceof RecordSaveError ) { this . setMessages ( error . getMessages ( ) ) ; return Observable . of ( error ) ; } throw Observable . throw ( error ) ; } ) . filter ( res => ! ( res instanceof RecordSaveError ) ) ; } delete ( ) { var query = this . getDeleteQuery ( ) ; return this . model . backend . deleteRecord ( query , this . uid ) . map ( ( ) => { this . isDeleted = true ; this . setMessages ( { } ) ; return this ; } ) . catch ( ( error , caught ) => { if ( error instanceof RecordSaveError ) { this . setMessages ( error . getMessages ( ) ) ; return Observable . throw ( error ) ; } throw error ; } ) ; } setMessages ( messages ) { this . messages = messages . common || [ ] ; this . fieldMessages . clear ( ) ; for ( let fieldName in messages . field ) { this . fieldMessages . set ( fieldName , messages . field [ fieldName ] ) ; } for ( let fieldName in messages . virtualField ) { this . host [ fieldName ] . setMessages ( messages . virtualField [ fieldName ] ) ; } this . messagesChanged . next ( this . host ) ; } lock ( ) { return this . model . backend . lockRecord ( this . host ) ; } unlock ( ) { return this . model . backend . unlockRecord ( this . host ) ; } isLocked ( ) { return this . model . backend . isRecordLocked ( this . host ) ; } hasEditPermission ( ) { return this . model . backend . hasRecordEditPermission ( this . host ) ; } hasDeletePermission ( ) { return this . model . backend . hasRecordDeletePermission ( this . host ) ; } extractRAWData ( ) { var rawData = { } ; rawData [ this . backend . modelNameField ] = this . model . name ; for ( let field of this . model . fields ) { if ( field . name in this . data ) { rawData [ field . name ] = field . fromInternalToRAW ( this . data [ field . name ] ) ; } } return rawData ; } clone ( withData = false , asNew = false , withAutofields = true , excludeFields = [ ] ) { var rawData = { } ; var recordClass = Object . getPrototypeOf ( this . host ) . constructor ; rawData [ this . backend . modelNameField ] = this . model . name ; for ( let field of this . model . getDbFields ( ) ) { var primaryKeyField = this . model . primaryKeys . indexOf ( field . name ) !== - 1 ; var autoField = this . model . autoFields . indexOf ( field . name ) !== - 1 ; if ( field . name in this . data && excludeFields . indexOf ( field . name ) == - 1 && ( ( ! asNew && primaryKeyField ) || ( withAutofields || ! autoField ) ) ) { rawData [ field . name ] = field . fromInternalToRAW ( this . data [ field . name ] ) ; } } var newRecord = new recordClass ( this . model , rawData , asNew ) ; newRecord . __director__ . isFull = this . isFull ; return newRecord ; } fitLookup ( lookupData ) { if ( lookupData . model !== this . model ) return false ; var recordCondition = this . getLookupData ( ) . condition ; var lookupCondition = lookupData . condition ; for ( let fieldName of Object . keys ( lookupCondition ) ) { var field = this . model . getField ( fieldName , null ) ; if ( ! field ) return false ; if ( ! field . compareValues ( recordCondition [ field . name ] , lookupCondition [ fieldName ] ) ) return false ; } return true ; } } export class Record extends ListRow { __director__ : RecordDirector ; constructor ( model , data ? , isNew ? ) { super ( model instanceof RecordDirector ? model : new ( model . backend . getRecordDirector ( model ) ) ( model , data , isNew ) ) ; if ( ! ( data instanceof RecordDirector ) ) this . __director__ . init ( data ) ; } toString ( ) { return this . __director__ . model . representRecord ( this ) ; } }