--- a/src/main/resources/db/db-data.sql Wed May 09 22:53:03 2012 +0200
+++ b/src/main/resources/db/db-data.sql Thu May 24 11:19:26 2012 +0200
@@ -137,3 +137,76 @@
VALUES
('company_status', nextval('code_list_item_id_seq'), true, 'company.status.inactive', 30, false, 0,
0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
+-- Service state
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('service_state', nextval('code_list_item_id_seq'), true, 'service.state.init', 10, true, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('service_state', nextval('code_list_item_id_seq'), true, 'service.state.active', 20, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('service_state', nextval('code_list_item_id_seq'), true, 'service.state.terminated', 30, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
+-- Currency
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('currency', nextval('code_list_item_id_seq'), false, 'CZK', 10, true, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('currency', nextval('code_list_item_id_seq'), false, 'EUR', 20, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('currency', nextval('code_list_item_id_seq'), false, 'USD', 30, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
+-- Payment period
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('payment_period', nextval('code_list_item_id_seq'), true, 'payment.period.initial', 10, true, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('payment_period', nextval('code_list_item_id_seq'), true, 'payment.period.monthly', 20, false, 12,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('payment_period', nextval('code_list_item_id_seq'), true, 'payment.period.quarterly', 30, false, 4,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('payment_period', nextval('code_list_item_id_seq'), true, 'payment.period.semiannually', 40, false, 2,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('payment_period', nextval('code_list_item_id_seq'), true, 'payment.period.annually', 50, false, 1,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
--- a/src/main/resources/db/db-schema.sql Wed May 09 22:53:03 2012 +0200
+++ b/src/main/resources/db/db-schema.sql Thu May 24 11:19:26 2012 +0200
@@ -210,6 +210,29 @@
"location" bigint not null,
"project" bigint not null
);
+create table "service" (
+ "name" varchar(100) not null,
+ "updated_at" timestamp not null,
+ "id" bigint primary key not null,
+ "to" timestamp,
+ "state" bigint not null,
+ "company" bigint not null,
+ "note" varchar(10240),
+ "from" timestamp not null,
+ "created_at" timestamp not null,
+ "created_by" bigint,
+ "updated_by" bigint
+ );
+create sequence "service_id_seq";
+create table "service_payment" (
+ "id" bigint primary key not null,
+ "service" bigint not null,
+ "direction" integer not null,
+ "period" bigint not null,
+ "amount" numeric(16,2) not null,
+ "currency" bigint not null
+ );
+create sequence "service_payment_id_seq";
-- foreign key constraints :
alter table "address" add foreign key ("city_id") references "city"("id");
alter table "city" add foreign key ("country_id") references "country"("id");
@@ -226,7 +249,12 @@
alter table "task" add foreign key ("state") references "code_list_item"("id");
alter table "task" add foreign key ("project_id") references "project"("id");
alter table "comment" add foreign key ("task_id") references "task"("id") on delete cascade;
+alter table "service" add foreign key ("company") references "company"("id");
+alter table "service" add foreign key ("state") references "code_list_item"("id");
alter table "company_contact" add foreign key ("entity") references "company"("id") on delete cascade;
+alter table "service_payment" add foreign key ("service") references "service"("id") on delete cascade;
+alter table "service_payment" add foreign key ("period") references "code_list_item"("id");
+alter table "service_payment" add foreign key ("currency") references "code_list_item"("id");
alter table "company_contact" add foreign key ("contact") references "contact"("id") on delete cascade;
alter table "user_contact" add foreign key ("entity") references "user"("id") on delete cascade;
alter table "user_contact" add foreign key ("contact") references "contact"("id") on delete cascade;
--- a/src/main/resources/db/schema-changes-0.2-0.3.sql Wed May 09 22:53:03 2012 +0200
+++ b/src/main/resources/db/schema-changes-0.2-0.3.sql Thu May 24 11:19:26 2012 +0200
@@ -8,3 +8,36 @@
alter table "project_location" add foreign key ("project") references "project"("id") on delete cascade;
alter table "project_location" add foreign key ("location") references "location"("id") on delete cascade;
alter table "project_location" add unique("project","location");
+
+-- service
+create table "service" (
+ "name" varchar(100) not null,
+ "updated_at" timestamp not null,
+ "id" bigint primary key not null,
+ "to" timestamp,
+ "state" bigint not null,
+ "company" bigint not null,
+ "note" varchar(10240),
+ "from" timestamp not null,
+ "created_at" timestamp not null,
+ "created_by" bigint,
+ "updated_by" bigint
+ );
+create sequence "service_id_seq";
+alter table "service" add foreign key ("company") references "company"("id");
+alter table "service" add foreign key ("state") references "code_list_item"("id");
+
+-- service payment
+create table "service_payment" (
+ "id" bigint primary key not null,
+ "service" bigint not null,
+ "direction" integer not null,
+ "period" bigint not null,
+ "amount" numeric(16,2) not null,
+ "currency" bigint not null
+ );
+create sequence "service_payment_id_seq";
+alter table "service_payment" add foreign key ("service") references "service"("id") on delete cascade;
+alter table "service_payment" add foreign key ("period") references "code_list_item"("id");
+alter table "service_payment" add foreign key ("currency") references "code_list_item"("id");
+
--- a/src/main/scala/bootstrap/liftweb/Boot.scala Wed May 09 22:53:03 2012 +0200
+++ b/src/main/scala/bootstrap/liftweb/Boot.scala Thu May 24 11:19:26 2012 +0200
@@ -22,6 +22,7 @@
import fis.crm.ui._
import fis.geo.ui._
import fis.pm.ui._
+import fis.sr.ui._
import fis.db.SquerylTxMgr
import net.datatables.DataTables
import net.liftweb.common._
@@ -63,7 +64,8 @@
ContactSnippet.menu,
CountrySnippet.menu,
CitySnippet.menu,
- LocationSnippet.menu
+ LocationSnippet.menu,
+ ServiceSnippet.menu
)
LiftRules.setSiteMap(SiteMap(menus:_*))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/base/model/Payment.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.base.model
+
+import fis.cl.model.{CodeList, CodeListItem, CodeListItemField}
+import java.math.MathContext
+import net.liftweb.record.{MetaRecord, Record}
+import net.liftweb.record.field._
+import net.tz.lift.model._
+import net.tz.lift.model.{FieldLabel => FL}
+import org.squeryl.annotations.Column
+
+trait Payment[T <: Record[T]] { self: T =>
+
+ val direction = new EnumField(this.asInstanceOf[T], PaymentDirection) with FL
+ {
+ override def defaultValue = PaymentDirection.Cost
+ override def get = PaymentDirection(super.get.id)
+ }
+
+ @Column("period")
+ val periodFld = new CodeListItemField(this.asInstanceOf[T], 'payment_period)
+ with FL
+ def period: PaymentPeriod = periodFld.item.dmap(PaymentPeriod.empty)(PaymentPeriod(_))
+
+ val amount = new DecimalField(this.asInstanceOf[T], MathContext.DECIMAL64, 2)
+ with FL
+ val currency = new CodeListItemField(this.asInstanceOf[T], 'currency) with FL
+}
+
+object PaymentDirection extends Enumeration {
+ type PaymentDirection = Value
+
+ val Cost = new L10nVal(1, "payment.direction.cost")
+ val Revenue = new L10nVal(2, "payment.direction.revenue")
+
+ class L10nVal(id: Int, n: String) extends Val(id, n) {
+ override def toString = l10n(n)
+ }
+}
+
+case class PaymentPeriod(perYear: Int, initial: Boolean, i: CodeListItem,
+ rank: Int)
+
+object PaymentPeriod {
+ def apply(i: CodeListItem): PaymentPeriod =
+ PaymentPeriod(i.i1.get, i.i1.get == 0, i, i.rank.get)
+
+ lazy val empty = apply(CodeListItem.createRecord)
+
+ private lazy val cl = CodeList('payment_period)
+
+ def periods = cl.items map(apply(_))
+}
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/base/ui/FieldTable.scala Wed May 09 22:53:03 2012 +0200
+++ b/src/main/scala/fis/base/ui/FieldTable.scala Thu May 24 11:19:26 2012 +0200
@@ -18,7 +18,7 @@
import net.liftweb.http.Templates
import net.liftweb.util._
import net.liftweb.util.Helpers._ // CSS transforms
-import scala.xml.NodeSeq
+import scala.xml.{NodeSeq, Text}
/**
* Template driven table, compatible w/ datatables.net.
@@ -29,7 +29,8 @@
Templates(List("templates-hidden", "datatable")) openOr NodeSeq.Empty
protected def cols(cs: Iterable[ReadableField]) =
- ".field-name *" #> (cs map(_.displayName))
+ ".field-name *" #> (cs map { f =>
+ f.displayNameHtml openOr Text(f.displayName) })
protected def cells(cs: Iterable[T]) =
".r" #> cs.map { v => "td *" #> (fields(v) map { _.asHtml }) }
--- a/src/main/scala/fis/crm/ui/CompanySnippet.scala Wed May 09 22:53:03 2012 +0200
+++ b/src/main/scala/fis/crm/ui/CompanySnippet.scala Thu May 24 11:19:26 2012 +0200
@@ -23,6 +23,8 @@
import fis.geo.model._
import fis.pm.model.CompanyProjects
import fis.pm.ui.ProjectTable
+import fis.sr.model.{CompanyServices, Service}
+import fis.sr.ui.{ServiceForm, ServiceSnippet, ServiceTable}
import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.http.js.JsCmds.RedirectTo
@@ -48,7 +50,7 @@
private val viewPre = Menu.param[Company]("company.view", l10n("Company"), parse,
encode) / prefix / * >> Title(c => i18n("Company %s", c.linkName)) >>
locTpl("company/view") >> Snippet("panel", panel) >>
- Snippet("contacts", contacts.contacts) >>
+ Snippet("services", services) >> Snippet("contacts", contacts.contacts) >>
Snippet("projects", projects) >> Hidden
private val editPre = Menu.param[Company]("company.edit", l10n("Edit"), parse,
@@ -61,18 +63,25 @@
Title(c => i18n("Delete company %s", c.linkName)) >> IfLoggedIn.testVal >>
locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden
+ private val createServicePre = Menu.param[Company]("company.create-service",
+ l10n("Create service"), parse, encode) / prefix / * / "create-service" >>
+ Title(c => i18n("Create service for company %s", c.linkName)) >>
+ IfLoggedIn.testVal >>
+ locTpl("entity/form") >> Snippet("form", createServiceForm) >> Hidden
+
private val listM = listPre >> SecNav(createPre).build
private val createM = createPre >> SecNav(listPre).build
private val viewM = viewPre >>
- (SecNav(editPre) + deletePre + contacts.chooseM).build
+ (SecNav(editPre) + deletePre + createServicePre + contacts.chooseM).build
private val editM = editPre >> SecNav(viewPre).build
private val deleteM = deletePre >> SecNav(viewPre).build
+ private val createServiceM = createServicePre >> SecNav(viewPre).build
private lazy val viewLoc = viewM.toLoc
private lazy val editLoc = editM.toLoc
private lazy val deleteLoc = deleteM.toLoc
- val menu = listM submenus(viewM, editM, createM, deleteM,
+ val menu = listM submenus(viewM, editM, createM, deleteM, createServiceM,
contacts.chooseM, contacts.confirmM)
private def cur = viewLoc.currentValue or editLoc.currentValue or
@@ -85,6 +94,9 @@
private def projects: CssTr = "*" #> cur.map { c =>
ProjectTable(CompanyProjects(c)) }
+ private def services: CssTr = "*" #> cur.map { c =>
+ ServiceTable(CompanyServices(c)) }
+
object url {
def view: Company => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
}
@@ -248,6 +260,16 @@
}
}
+ private object createServiceForm extends ServiceForm {
+ override def localSetup() {
+ createServiceM.currentValue.foreach { c => service.company(c.id) }
+ }
+
+ protected def onSuccess(s: Service) {
+ ServiceSnippet.url.view(s).foreach { u => S redirectTo u }
+ }
+ }
+
}
// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/model/Service.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.model
+
+import fis.aaa.model._
+import fis.base.model.Entity
+import fis.cl.model._
+import fis.crm.model._
+import net.liftweb.common._
+import net.liftweb.record.{MetaRecord, Record}
+import net.liftweb.record.field._
+import net.tz.lift.model._
+import net.tz.lift.model.{FieldLabel => FL}
+import org.squeryl.annotations.Column
+
+class Service private() extends Record[Service] with Entity[Service] {
+ def meta = Service
+
+ val company = new LongField(this) with CompanyField with FL {
+ override def toForm = Full(<span class="uneditable-input">{asHtml}</span>)
+ }
+
+ @Column("state")
+ protected[sr] val stateFld = new CodeListItemField(this, 'service_state)
+ with FL
+
+ val from = new JodaDateMidnightField(this) with FL
+ val to = new OptionalJodaDateMidnightField(this) with FL
+
+ def state = stateFld.item.map(ServiceState(_))
+}
+
+object Service extends Service with MetaRecord[Service]
+
+case class ServiceState(id: Long)
+object ServiceState {
+ def apply(i: CodeListItem): ServiceState = ServiceState(i.id)
+}
+
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/model/ServiceCrud.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.model
+
+import fis.base.model.RecordCrud
+
+trait ServiceCrud extends RecordCrud[Service] {
+ val table = ServiceRepoSchema.serviceT
+}
+
+object ServiceCrud extends ServiceCrud
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/model/ServicePayment.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.model
+
+import fis.aaa.model._
+import fis.base.model.Payment
+import fis.cl.model._
+import fis.crm.model._
+import java.math.MathContext
+import net.liftweb.common._
+import net.liftweb.record.{MetaRecord, Record}
+import net.liftweb.record.field._
+import net.tz.lift.model._
+import net.tz.lift.model.{FieldLabel => FL}
+import org.squeryl.annotations.Column
+import net.liftweb.squerylrecord.KeyedRecord
+
+class ServicePayment private() extends Record[ServicePayment] with
+ KeyedRecord[Long] with Payment[ServicePayment] {
+
+ @Column(name="id")
+ val idField = new LongField(this)
+
+ def meta = ServicePayment
+
+ val service = new LongField(this)
+}
+
+object ServicePayment extends ServicePayment with MetaRecord[ServicePayment]
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/model/ServicePaymentCrud.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.model
+
+import fis.base.model.RecordCrud
+
+trait ServicePaymentCrud extends RecordCrud[ServicePayment] {
+ val table = ServiceRepoSchema.servicePaymentT
+}
+
+object ServicePaymentCrud extends ServicePaymentCrud
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/model/ServiceRepoSchema.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.model
+
+import fis.crm.model.{Company, CrmSchema}
+import net.liftweb.squerylrecord.RecordTypeMode._
+
+trait ServiceRepoSchema extends CrmSchema {
+ /* service */
+ val serviceT = tableWithSeq[Service]
+
+ val serviceCompany = oneToManyRelation(companyT, serviceT).
+ via((c, s) => (c.id === s.company))
+
+ val serviceState = oneToManyRelation(cli, serviceT).
+ via((i, s) => (i.id === s.stateFld))
+
+ /* service payment */
+ val servicePaymentT = tableWithSeq[ServicePayment]
+
+ val servicePaymentService = oneToManyRelation(serviceT, servicePaymentT).
+ via((s, p) => (s.id === p.service))
+ servicePaymentService.foreignKeyDeclaration.constrainReference(onDelete cascade)
+
+ val servicePaymentPeriod = oneToManyRelation(cli, servicePaymentT).
+ via((i, p) => (i.id === p.periodFld))
+
+ val servicePaymentCurrency = oneToManyRelation(cli, servicePaymentT).
+ via((i, p) => (i.id === p.currency))
+
+}
+
+object ServiceRepoSchema extends ServiceRepoSchema
+
+object CompanyServices {
+ def apply(c: Company): Iterable[Service] =
+ from(ServiceRepoSchema.serviceCompany.left(c))(s =>
+ select(s) orderBy(s.name asc))
+}
+
+object ServicePayments {
+ def apply(s: Service): Iterable[ServicePayment] =
+ from(ServiceRepoSchema.servicePaymentService.left(s))(p => select(p))
+}
+
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/ui/ServiceForm.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.ui
+
+import fis.base.ui._
+import fis.sr.model._
+import net.liftweb.http._
+import net.liftweb.util._
+import net.tz.lift.model._
+
+abstract class ServiceForm extends HorizontalScreen with CancelButton with
+ SaveButton {
+
+ protected object service extends ScreenVar[Service](Service.createRecord)
+
+ protected def fields(s: Service): List[BaseField] = List(s.name, s.company,
+ s.stateFld, s.from, s.to, s.note)
+
+ override def screenFields = fields(service)
+
+ protected def onSuccess(s: Service): Unit
+
+ def finish() {
+ for {
+ s <- ServiceCrud.save(service)
+ } {
+ S notice l10n("Service %s saved.", s.linkName)
+ onSuccess(s)
+ }
+ }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/ui/ServicePaymentTable.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.ui
+
+import fis.sr.model._
+import fis.base.ui._
+import net.liftweb.http.Templates
+import net.liftweb.util.ReadableField
+import scala.xml.NodeSeq
+
+trait ServicePaymentTable extends FieldTable[ServicePayment] {
+
+ protected override def load =
+ Templates(List("templates-hidden", "nodatatable")) openOr NodeSeq.Empty
+
+ protected override def fields(p: ServicePayment): Iterable[ReadableField] =
+ Seq(p.periodFld, p.amount, p.currency)
+
+ def apply(l: Iterable[ServicePayment]) = build(ServicePayment, l)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/ui/ServiceSnippet.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.ui
+
+import fis.aaa.ui.IfLoggedIn
+import fis.base.model.{PaymentDirection, ReadOnlyField}
+import fis.base.ui._
+import fis.crm.ui.CompanySnippet
+import fis.sr.model._
+import net.liftweb.common._
+import net.liftweb.http._
+import net.liftweb.sitemap._
+import net.liftweb.sitemap.Loc._
+import net.liftweb.sitemap.Menu._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+import net.tz.lift.model._
+import net.tz.lift.snippet._
+import scala.xml.{Elem, NodeSeq, Text}
+
+object ServiceSnippet extends ServiceCrud with EntitySnippet[Service] {
+ val prefix = "service"
+
+ private val listM = Menu("service.list", l10n("Services")) / prefix >>
+ Title(_ => i18n("Services")) >>
+ locTpl("entity/list") >> Snippet("list", ClearNodes) >> Hidden
+
+ private val viewPre = Menu.param[Service]("service.view", l10n("Service"),
+ parse, encode) / prefix / * >> Title(t => i18n("Service %s", t.linkName)) >>
+ locTpl("service/view") >> Snippet("panel", panel) >>
+ Snippet("payments", payments) >> Hidden
+
+ private val editPre = Menu.param[Service]("service.edit", l10n("Edit"), parse,
+ encode) / prefix / * / EDIT >>
+ Title(t => i18n("Edit service %s", t.linkName)) >> IfLoggedIn.testVal >>
+ locTpl("entity/form") >> Snippet("form", form) >> Hidden
+
+ private val deletePre = Menu.param[Service]("service.delete", l10n("Delete"),
+ parse, encode) / prefix / * / DELETE >>
+ Title(t => i18n("Delete service %s", t.linkName)) >> IfLoggedIn.testVal >>
+ locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden
+
+ private val addCostM = Menu.param[Service]("service.addCost", l10n("Add"),
+ parse, encode) / prefix / * / "add-cost" >>
+ Title(t => i18n("Add cost to service %s", t.linkName)) >>
+ IfLoggedIn.testVal >> SecNav(viewPre).build >>
+ locTpl("entity/form") >> Snippet("form", paymentF) >> Hidden
+
+ private val addRevenueM = Menu.param[Service]("service.addRevenue", l10n("Add"),
+ parse, encode) / prefix / * / "add-revenue" >>
+ Title(t => i18n("Add revenue to service %s", t.linkName)) >>
+ IfLoggedIn.testVal >> SecNav(viewPre).build >>
+ locTpl("entity/form") >> Snippet("form", paymentF) >> Hidden
+
+ type PaS = (ServicePayment, Service)
+ private object paymentMemo extends RequestMemoize[String, Box[PaS]]()
+ private def parsePaS(id: String): Box[PaS] = paymentMemo(id, for {
+ p <- ServicePaymentCrud.get(id)
+ s <- get(p.service.get)
+ } yield (p, s))
+
+ private val editPaymentM = Menu.param[PaS]("service.payment.edit",
+ l10n("Edit"), parsePaS _, _._1.id.toString) / prefix / "payment" / * / EDIT >>
+ Title(p => i18n("Edit %s for service %s", p._1.direction, p._2.linkName)) >>
+ IfLoggedIn.testVal >>
+ locTpl("entity/form") >> Snippet("form", paymentF) >> Hidden
+
+ private val deletePaymentM = Menu.param[PaS]("service.payment.delete",
+ l10n("Delete"), parsePaS _, _._1.id.toString) / prefix / "payment" / * / DELETE >>
+ Title(p => i18n("Delete %s for service %s", p._1.direction, p._2.linkName)) >>
+ IfLoggedIn.testVal >>
+ locTpl("entity/form") >> Snippet("form", deletePaymentF) >> Hidden
+
+ private val viewM = viewPre >> (SecNav(editPre) + deletePre).build
+ private val editM = editPre >> SecNav(viewPre).build
+ private val deleteM = deletePre >> SecNav(viewPre).build
+
+ private lazy val viewLoc = viewM.toLoc
+ private lazy val editLoc = editM.toLoc
+ private lazy val deleteLoc = deleteM.toLoc
+
+ val menu = listM submenus(viewM, editM, deleteM, addCostM, addRevenueM,
+ editPaymentM, deletePaymentM)
+
+ private def cur = viewLoc.currentValue or editLoc.currentValue or
+ deleteLoc.currentValue
+
+ private def panel: CssTr = "*" #> cur.map { t => ViewPanel(fields(t)) }
+
+ object url {
+ def view: Service => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
+ }
+
+ private def fields(s: Service) = List(s.name, s.company, s.stateFld,
+ s.from, s.to, s.note)
+
+ private case class ServiceLink(s: Service) extends EntityLink[Service](s,
+ url.view)
+
+ EntityLink.register[Service](ServiceLink(_))
+
+ private object form extends ServiceForm {
+
+ override def localSetup() {
+ cur.foreach(service(_))
+ }
+
+ protected def onSuccess(s: Service) {
+ S.redirectTo(viewLoc.calcHref(s))
+ }
+ }
+
+ private object deleteF extends HorizontalScreen with CancelButton with
+ DeleteButton {
+
+ val confirm = field(l10n("Really delete this service?"), false)
+
+ def finish() {
+ for {
+ s <- deleteLoc.currentValue if confirm
+ c <- s.company.vend
+ u <- CompanySnippet.url.view(c)
+ r <- delete(s)
+ n <- r.box(s.linkName)
+ } {
+ S notice l10n("Service %s deleted.", n)
+ S redirectTo u
+ }
+ }
+ }
+
+ def payments: CssTr = "*" #> cur.map { s =>
+ val (cost, revenue) = (ServicePayments(s).toSeq
+ sortBy(_.period.rank)
+ partition(_.direction.get == PaymentDirection.Cost))
+ ".cost" #> new PaymentTable(s, addCostM).apply(cost) &
+ ".revenue" #> new PaymentTable(s, addRevenueM).apply(revenue)
+ }
+
+ private class PaymentTable(s: Service, addM: ParamMenuable[Service]) extends
+ ServicePaymentTable {
+
+ private def actionFld(p: ServicePayment) = new ReadOnlyField("actions", "",
+ NodeSeq.Empty, Empty) {
+
+ override def displayNameHtml =
+ Full(a(addM.toLoc.calcHref(s))(addM.linkText(s)))
+
+ override def asHtml = {
+ val pas = (p, s)
+ a(editPaymentM.toLoc.calcHref(pas))(editPaymentM.linkText(pas)) ++
+ Text(" ") ++
+ a(deletePaymentM.toLoc.calcHref(pas))(deletePaymentM.linkText(pas))
+ }
+ }
+
+ protected override def fields(p: ServicePayment) = super.fields(p) ++
+ (addM.toLoc.testAccess == Left(true)).box(actionFld(p))
+ }
+
+ private object paymentF extends HorizontalScreen with CancelButton with
+ SaveButton with ServicePaymentCrud {
+
+ object service extends ScreenVar[Box[Service]](Empty)
+ object payment extends ScreenVar[Box[ServicePayment]](Empty)
+
+ override def screenFields: List[BaseField] = payment.toList flatMap { p =>
+ List(p.periodFld, p.amount, p.currency) }
+
+ override def localSetup() {
+ val p = ServicePayment.createRecord
+ addCostM.toLoc.currentValue.foreach { s =>
+ service(Full(s))
+ payment(Full(p.direction(PaymentDirection.Cost).service(s.id))) }
+ addRevenueM.toLoc.currentValue.foreach { s =>
+ service(Full(s))
+ payment(Full(p.direction(PaymentDirection.Revenue).service(s.id))) }
+ editPaymentM.toLoc.currentValue.foreach { pas =>
+ service(Full(pas._2))
+ payment(Full(pas._1))
+ }
+ }
+
+ def finish() { for {
+ s <- service
+ u <- url.view(s)
+ p1 <- payment
+ p <- save(p1)
+ } {
+ S notice l10n("Payment saved.")
+ S redirectTo u
+ }}
+ }
+
+ private object deletePaymentF extends HorizontalScreen with CancelButton with
+ DeleteButton with ServicePaymentCrud {
+
+ val confirm = field(l10n("Really delete this payment?"), false)
+
+ def finish() {
+ for {
+ pas <- deletePaymentM.toLoc.currentValue if confirm
+ u <- url.view(pas._2)
+ r <- delete(pas._1)
+ } {
+ S notice l10n("Payment deleted.")
+ S redirectTo u
+ }
+ }
+ }
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/sr/ui/ServiceTable.scala Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package fis.sr.ui
+
+import fis.sr.model._
+import fis.base.ui._
+
+object ServiceTable extends FieldTable[Service] {
+ def fields(s: Service) = EntityLink(s) ++ Seq(s.company, s.stateFld, s.from,
+ s.to, s.note)
+ def apply(l: Iterable[Service]) = build(Service, l)
+}
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/top/model/FisDbSchema.scala Wed May 09 22:53:03 2012 +0200
+++ b/src/main/scala/fis/top/model/FisDbSchema.scala Thu May 24 11:19:26 2012 +0200
@@ -21,6 +21,7 @@
import fis.crm.model.CrmSchema
import fis.geo.model.GeoSchema
import fis.pm.model.PmSchema
+import fis.sr.model.ServiceRepoSchema
trait FisDbSchema extends BaseSchema
with CodeListSchema
@@ -28,6 +29,7 @@
with CrmSchema
with GeoSchema
with PmSchema
+ with ServiceRepoSchema
object FisDbSchema extends FisDbSchema
--- a/src/main/scala/net/tz/lift/model/JodaTimeField.scala Wed May 09 22:53:03 2012 +0200
+++ b/src/main/scala/net/tz/lift/model/JodaTimeField.scala Thu May 24 11:19:26 2012 +0200
@@ -90,6 +90,29 @@
}
/**
+ * Optional Joda date field.
+ */
+class OptionalJodaDateMidnightField[T <: Record[T]](rec: T) extends
+ OptionalDateTimeField(rec) {
+ override def setFromAny(in: Any): Box[Calendar] = in match {
+ case dt: AbstractDateTime => setBox(Full(dt.toGregorianCalendar))
+ case x => super.setFromAny(x)
+ }
+
+ def set(dt: Box[AbstractDateTime]): Box[Calendar] =
+ set(dt map(_.toGregorianCalendar))
+
+ def date = get map(new DateMidnight(_))
+
+ override def toString = date map(AsDateMidnight(_)) getOrElse ""
+
+ override def asHtml = Text(toString)
+ override def toForm = Full(SHtml.text(toString,
+ s => setBox(AsDateMidnight(s) map {_.toGregorianCalendar}),
+ "class" -> "date"))
+}
+
+/**
* Joda date-time converters.
*/
object AsDateTime {
@@ -106,8 +129,10 @@
val fmt = DateTimeFormat.forPattern("dd.MM.yyyy")
def apply(d: DateMidnight): String = fmt.print(d)
- def apply(s: String): Box[DateMidnight] = tryo {
- fmt.parseDateTime(s).toDateMidnight }
+ def apply(s: String): Box[DateMidnight] = s match {
+ case null | "" => Empty
+ case s => tryo { fmt.parseDateTime(s).toDateMidnight }
+ }
}
// vim: set ts=2 sw=2 et:
--- a/src/main/webapp/company/view.html Wed May 09 22:53:03 2012 +0200
+++ b/src/main/webapp/company/view.html Thu May 24 11:19:26 2012 +0200
@@ -13,6 +13,12 @@
</div> <!-- /row -->
<div class="row section">
<div class="span12">
+ <h3><span class="lift:loc?locid=Services"></span></h3>
+ <span class="lift:services"></span>
+ </div>
+ </div> <!-- /row -->
+ <div class="row section">
+ <div class="span12">
<h3><span class="lift:loc?locid=Contacts"></span></h3>
<span class="lift:contacts"></span>
</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/service/view.html Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+ <title>Entity View</title>
+ </head>
+ <body class="lift:content_id=main">
+ <div id="main" class="lift:surround?with=default;at=content">
+ <div class="row">
+ <div class="span12">
+ <span class="lift:panel"></span>
+ </div>
+ </div> <!-- /row -->
+ <div class="row section">
+ <span class="lift:payments">
+ <div class="span5">
+ <h3><span class="lift:loc?locid=payment.direction.cost"></span></h3>
+ <span class="cost"></span>
+ </div>
+ <div class="span5">
+ <h3><span class="lift:loc?locid=payment.direction.revenue"></span></h3>
+ <span class="revenue"></span>
+ </div>
+ </span>
+ </div> <!-- /row -->
+ </div>
+ </body>
+</html>
+
+
--- a/src/main/webapp/templates-hidden/_resources.html Wed May 09 22:53:03 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources.html Thu May 24 11:19:26 2012 +0200
@@ -264,6 +264,57 @@
<res name="comment.note" lang="en" default="true">Comment</res>
+ <!-- service
+ default strings:
+ Service
+ Service %s
+ Edit service %s
+ Delete service %s
+ Really delete this service?
+ Service %s deleted.
+ Service %s saved.
+ Create service
+ Create service for company %s
+ Payment saved.
+ Add cost to service %s
+ Add revenue to service %s
+ Edit %s for service %s
+ Payment deleted.
+ Really delete this payment?
+ Delete %s for service %s
+ -->
+ <res name="Services" lang="en" default="true">Services</res>
+ <!-- service fields -->
+ <res name="service.name" lang="en" default="true">Name</res>
+ <res name="service.note" lang="en" default="true">Note</res>
+ <res name="service.company" lang="en" default="true">Company</res>
+ <res name="service.stateFld" lang="en" default="true">State</res>
+ <res name="service.from" lang="en" default="true">From</res>
+ <res name="service.to" lang="en" default="true">To</res>
+
+ <!-- service states -->
+ <res name="service.state.init" lang="en" default="true">Initialization</res>
+ <res name="service.state.active" lang="en" default="true">Active</res>
+ <res name="service.state.terminated" lang="en" default="true">Terminated</res>
+
+
+ <!-- payments -->
+ <res name="payment.direction.cost" lang="en" default="true">Cost</res>
+ <res name="payment.direction.revenue" lang="en" default="true">Revenue</res>
+ <res name="payment.period.initial" lang="en" default="true">Initial</res>
+ <res name="payment.period.monthly" lang="en" default="true">Monthly</res>
+ <res name="payment.period.quarterly" lang="en" default="true">Quarterly</res>
+ <res name="payment.period.semiannually" lang="en" default="true">Semiannually</res>
+ <res name="payment.period.annually" lang="en" default="true">Annually</res>
+
+ <!-- service payments -->
+ <res name="servicePayment.direction" lang="en" default="true">Payment type</res>
+ <res name="servicePayment.periodFld" lang="en" default="true">Period</res>
+ <res name="servicePayment.amount" lang="en" default="true">Amount</res>
+ <res name="servicePayment.currency" lang="en" default="true">Currency</res>
+
+
+
<!--
vim: et sw=2 ts=2
-->
--- a/src/main/webapp/templates-hidden/_resources_cs.html Wed May 09 22:53:03 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources_cs.html Thu May 24 11:19:26 2012 +0200
@@ -250,6 +250,54 @@
<res name="comment.note" lang="cs">Komentář</res>
+ <!-- service -->
+ <res name="Service" lang="cs">Služba</res>
+ <res name="Service %s" lang="cs">Služba %s</res>
+ <res name="Edit service %s" lang="cs">Upravit službu %s</res>
+ <res name="Delete service %s" lang="cs">Smazat službu %s</res>
+ <res name="Really delete this service?" lang="cs">Skutečně smazat službu?</res>
+ <res name="Service %s deleted." lang="cs">Služba %s smazána.</res>
+ <res name="Service %s saved." lang="cs">Služba %s uložena.</res>
+ <res name="Create service" lang="cs">Vytvořit službu</res>
+ <res name="Create service for company %s" lang="cs">Vytvořit službu pro společnost %s</res>
+ <res name="Payment saved." lang="cs">Platba uložena.</res>
+ <res name="Add cost to service %s" lang="cs">Přidat náklady ke službě %s</res>
+ <res name="Add revenue to service %s" lang="cs">Přidat výnosy ke službě %s</res>
+ <res name="Edit %s for service %s" lang="cs">Upravit %s pro službu %s</res>
+ <res name="Payment deleted." lang="cs">Platba smazána.</res>
+ <res name="Really delete this payment?" lang="cs">Skutečně smazat platbu?</res>
+ <res name="Delete %s for service %s" lang="cs">Smazat %s pro službu %s</res>
+ <res name="Services" lang="cs">Služby</res>
+ <!-- service fields -->
+ <res name="service.name" lang="cs">Název</res>
+ <res name="service.note" lang="cs">Poznámka</res>
+ <res name="service.company" lang="cs">Společnost</res>
+ <res name="service.stateFld" lang="cs">Stav</res>
+ <res name="service.from" lang="cs">Od</res>
+ <res name="service.to" lang="cs">Do</res>
+
+ <!-- service states -->
+ <res name="service.state.init" lang="en" default="true">Ve výstavbě</res>
+ <res name="service.state.active" lang="en" default="true">Aktivní</res>
+ <res name="service.state.terminated" lang="en" default="true">Ukončena</res>
+
+ <!-- payments -->
+ <res name="payment.direction.cost" lang="cs">Náklady</res>
+ <res name="payment.direction.revenue" lang="cs">Výnosy</res>
+ <res name="payment.period.initial" lang="cs">Počáteční</res>
+ <res name="payment.period.monthly" lang="cs">Měsíční</res>
+ <res name="payment.period.quarterly" lang="cs">Čtvrtletní</res>
+ <res name="payment.period.semiannually" lang="cs">Pololetní</res>
+ <res name="payment.period.annually" lang="cs">Roční</res>
+
+ <!-- service payments -->
+ <res name="servicePayment.direction" lang="cs">Typ platby</res>
+ <res name="servicePayment.periodFld" lang="cs">Perioda</res>
+ <res name="servicePayment.amount" lang="cs">Částka</res>
+ <res name="servicePayment.currency" lang="cs">Měna</res>
+
+
+
<!--
vim: et sw=2 ts=2
-->
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/templates-hidden/nodatatable.html Thu May 24 11:19:26 2012 +0200
@@ -0,0 +1,12 @@
+<table class="table table-striped table-bordered table-condensed">
+ <thead>
+ <tr>
+ <th class="field-name">Field Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="r">
+ <td>Cell value</td>
+ </tr>
+ </tbody>
+</table>