Set up pricing for most non-stair objects

master
Pete Ley 5 months ago
parent 9d7be79914
commit e71497067b

@ -4,18 +4,20 @@ from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
from sqlalchemy import create_engine, text
from sf_auth.models import User
from core.models import (
Project,
ProjectCategory,
Location,
Company,
Scope,
from core.models.client import Company
from core.models.location import Location
from core.models.product import (
Finish,
StairTreadStyle,
LandingTreadStyle,
RailStyle,
MiscProduct,
)
from core.models.project import (
Project,
ProjectCategory,
Scope,
)
class Command(BaseCommand):
@ -70,7 +72,8 @@ class Command(BaseCommand):
Group.objects.all().delete()
self.import_users(*args, **options)
self.import_project_categories(*args, **options)
self.import_finishes()
# these aren't actually that useful
# self.import_finishes()
self.fix_scope_floor_landing_count()
self.import_projects(*args, **options)
self.stdout.write('''
@ -90,7 +93,7 @@ Import summary:
ProjectCategory.objects.count(),
Location.objects.count(),
Company.objects.count(),
Finish.objects.count(),
0, # Finish.objects.count(),
self._queries_ran,
))
@ -280,7 +283,7 @@ Import summary:
scope.flight_count = legacy_scope.flight_count or 0
scope.mid_landing_count = legacy_scope.mid_landing_count or 0
scope.floor_landing_count = legacy_scope.floor_landing_count or 0
self.set_scope_finishes(scope)
# self.set_scope_finishes(scope)
self.import_scope_products(scope)
scope.save()

@ -1,9 +1,7 @@
from django.core.management.base import BaseCommand
from wagtail.models import Site, Page
from core.models import (
LandingPage,
ProjectIndexPage,
)
from core.models.frontend import LandingPage
from core.models.project import ProjectIndexPage
class Command(BaseCommand):

@ -1,5 +1,6 @@
# Generated by Django 4.2.6 on 2023-12-11 17:32
# Generated by Django 4.2.8 on 2023-12-13 19:56
import core.models.bid
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
@ -15,10 +16,10 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailimages', '0025_alter_image_file_alter_rendition_file'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wagtailcore', '0089_log_entry_data_json_null_to_object'),
('wagtaildocs', '0012_uploadeddocument'),
('wagtailimages', '0025_alter_image_file_alter_rendition_file'),
('wagtailcore', '0089_log_entry_data_json_null_to_object'),
]
operations = [
@ -36,7 +37,7 @@ class Migration(migrations.Migration):
options={
'abstract': False,
},
bases=(wagtail.search.index.Indexed, models.Model),
bases=(core.models.bid.ScopeSummaryMixin, wagtail.search.index.Indexed, models.Model),
),
migrations.CreateModel(
name='BidRevision',
@ -48,9 +49,9 @@ class Migration(migrations.Migration):
('bid', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.PROTECT, related_name='revisions', to='core.bid')),
],
options={
'ordering': ['sort_order'],
'abstract': False,
'ordering': ['-number'],
},
bases=(core.models.bid.ScopeSummaryMixin, models.Model),
),
migrations.CreateModel(
name='BuildingCode',
@ -83,6 +84,18 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('sort_order', models.IntegerField(default=0, help_text='Lower numbers sort earlier')),
('galv_base_price', models.DecimalField(blank=True, decimal_places=2, help_text='per pound', max_digits=16, null=True, verbose_name='base price (stairs/landings)')),
('galv_base_price_rail', models.DecimalField(blank=True, decimal_places=2, help_text='per pound', max_digits=16, null=True, verbose_name='base price (rails)')),
('galv_base_price_column', models.DecimalField(blank=True, decimal_places=2, help_text='per pound', max_digits=16, null=True, verbose_name='base price (columns)')),
('galv_pounds_riser', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='pounds per riser-foot')),
('galv_pounds_sqft', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='pounds per landing sqft')),
('galv_pounds_sr', models.DecimalField(blank=True, decimal_places=2, help_text='stair rail and guard rail', max_digits=16, null=True, verbose_name='pounds per linear foot')),
('galv_pounds_wr', models.DecimalField(blank=True, decimal_places=2, help_text='wall rail', max_digits=16, null=True, verbose_name='pounds per linear foot')),
('galv_pounds_column', models.DecimalField(blank=True, decimal_places=2, help_text='columns', max_digits=16, null=True, verbose_name='pounds per linear foot')),
('price_riser', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='price per riser')),
('price_sqft', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='price per landing sqft')),
('price_ex_sr', models.DecimalField(blank=True, decimal_places=2, help_text='per linear foot', max_digits=16, null=True, verbose_name='extra stair rail price')),
('price_ex_wr', models.DecimalField(blank=True, decimal_places=2, help_text='per linear foot', max_digits=16, null=True, verbose_name='extra wall rail price')),
],
options={
'verbose_name_plural': 'finishes',
@ -122,6 +135,7 @@ class Migration(migrations.Migration):
('sort_order', models.IntegerField(default=0, help_text='Lower numbers sort earlier')),
('description', wagtail.fields.RichTextField(blank=True, null=True)),
('base_price', models.DecimalField(blank=True, decimal_places=2, help_text='per square foot', max_digits=16, null=True)),
('floor_price_adj', models.DecimalField(blank=True, decimal_places=2, help_text='relative to mid-level landings, per square foot', max_digits=16, null=True, verbose_name='floor-level adjustment')),
],
options={
'ordering': ['sort_order', 'name'],
@ -229,7 +243,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(default=0, help_text='Lower numbers sort earlier')),
('name', models.CharField(max_length=255)),
('install_price', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True)),
('install_price', models.DecimalField(blank=True, decimal_places=2, help_text='per floor', max_digits=16, null=True)),
('uses_embeds', models.BooleanField()),
('uses_hangers', models.BooleanField()),
],
@ -277,8 +291,8 @@ class Migration(migrations.Migration):
name='Scope',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('legacy_id', models.IntegerField(blank=True, null=True)),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('legacy_id', models.IntegerField(blank=True, null=True)),
('name', models.CharField(help_text='e.g. "Stair 1" or "Machine Room Stair"', max_length=255)),
('alt_name', models.CharField(blank=True, help_text='Stair name in client documents, if different', max_length=255, null=True)),
('grid', models.CharField(blank=True, max_length=16, null=True)),

@ -11,7 +11,6 @@ from wagtail.search import index
from sf_auth.models import User
from .client import Company
from .product import LandingPostType
from .project import SlabConstruction
from .location import STATES
@ -134,7 +133,7 @@ class BaseBidScope(Orderable, ClusterableModel):
null=True,
)
grid = models.CharField(max_length=16, blank=True, null=True)
slab_construction = models.ForeignKey(SlabConstruction, on_delete=models.PROTECT)
slab_construction = models.ForeignKey('core.SlabConstruction', on_delete=models.PROTECT)
takeoff = models.JSONField(null=True)
TAKEOFF_KEYS = {
@ -201,3 +200,12 @@ class BidDocument(Orderable, ClusterableModel):
def name(self):
return self.doc.title
# class BaseAdder(Orderable, ClusterableModel):
# TYPE_CHOICES = [
# ('each', 'Each'),
# ('floor', 'Per floor'),
# ('flight', 'Per flight'),
# ('riser', 'Per riser'),
# ]

@ -2,7 +2,6 @@ from django.db import models
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.images.models import Image
from wagtail.models import Page, ParentalKey
from wagtail.snippets.models import register_snippet
class LandingPage(Page):
@ -12,7 +11,6 @@ class LandingPage(Page):
]
@register_snippet
class LandingPageImage(models.Model):
owner = ParentalKey(
LandingPage,
@ -31,7 +29,6 @@ class LandingPageImage(models.Model):
return self.caption
@register_snippet
class LandingPageFeature(models.Model):
owner = ParentalKey(
LandingPage,

@ -1,15 +1,106 @@
from django.db import models
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import ObjectList, MultiFieldPanel, FieldPanel, FieldRowPanel
from wagtail.fields import RichTextField
from wagtail.models import Orderable
from .util import ChoiceList, cash_field
class Finish(ChoiceList):
# special properties only for galv finish
class GalvMixin(models.Model):
class Meta:
abstract = True
galv_base_price = cash_field(
verbose_name='base price (stairs/landings)',
help_text='per pound',
blank=True,
null=True,
)
galv_base_price_rail = cash_field(
verbose_name='base price (rails)',
help_text='per pound',
blank=True,
null=True,
)
galv_base_price_column = cash_field(
verbose_name='base price (columns)',
help_text='per pound',
blank=True,
null=True,
)
galv_pounds_riser = cash_field(
verbose_name='pounds per riser-foot',
blank=True,
null=True,
)
galv_pounds_sqft = cash_field(
verbose_name='pounds per landing sqft',
blank=True,
null=True,
)
galv_pounds_sr = cash_field(
verbose_name='pounds per linear foot',
help_text='stair rail and guard rail',
blank=True,
null=True,
)
galv_pounds_wr = cash_field(
verbose_name='pounds per linear foot',
help_text='wall rail',
blank=True,
null=True,
)
galv_pounds_column = cash_field(
verbose_name='pounds per linear foot',
help_text='columns',
blank=True,
null=True,
)
galv_panel = ObjectList(
[
MultiFieldPanel([
FieldRowPanel([
FieldPanel('galv_base_price'),
FieldPanel('galv_base_price_rail'),
FieldPanel('galv_base_price_column'),
]),
FieldRowPanel([
FieldPanel('galv_pounds_riser'),
FieldPanel('galv_pounds_sqft'),
]),
FieldRowPanel([
FieldPanel('galv_pounds_sr'),
FieldPanel('galv_pounds_wr'),
FieldPanel('galv_pounds_column'),
]),
]),
],
heading='Galv Pricing',
)
class Finish(GalvMixin, ChoiceList):
class Meta(ChoiceList.Meta):
verbose_name_plural = 'finishes'
price_riser = cash_field(verbose_name='price per riser', blank=True, null=True)
price_sqft = cash_field(verbose_name='price per landing sqft', blank=True, null=True)
price_ex_sr = cash_field(
verbose_name='extra stair rail price',
help_text='per linear foot',
blank=True,
null=True,
)
price_ex_wr = cash_field(
verbose_name='extra wall rail price',
help_text='per linear foot',
blank=True,
null=True,
)
class StringerType(ChoiceList):
pass
@ -44,6 +135,12 @@ class StairPricePoint(models.Model):
class LandingTreadStyle(Product):
base_price = cash_field(help_text='per square foot', blank=True, null=True)
floor_price_adj = cash_field(
verbose_name='floor-level adjustment',
help_text='relative to mid-level landings, per square foot',
blank=True,
null=True,
)
stair_style = models.ForeignKey(
StairTreadStyle,
verbose_name='corresponding stair tread style',
@ -52,6 +149,11 @@ class LandingTreadStyle(Product):
null=True,
)
def floor_price(self):
if self.floor_price_adj:
return self.base_price + self.floor_price_adj
return self.base_price
def clean(self):
self.description = self.stair_style.description
@ -65,6 +167,11 @@ class RailStyle(Product):
null=True,
)
def add_price(self):
if self.add_price_adj:
return self.base_price + self.add_price_adj
return self.base_price
class MiscProduct(Product):
pass

@ -34,7 +34,7 @@ class BuildingCode(ChoiceList):
class SlabConstruction(ChoiceList):
name = models.CharField(max_length=255)
install_price = cash_field(blank=True, null=True)
install_price = cash_field(help_text='per floor', blank=True, null=True)
uses_embeds = models.BooleanField()
uses_hangers = models.BooleanField()

@ -0,0 +1,26 @@
{% extends "wagtailsnippets/snippets/edit.html" %}
{% block extra_js %}
{{ block.super }}
<script>
function hideElem(id) { document.querySelector(id).style = 'display:none' }
window.onload = function() {
const nameField = document.querySelector('#id_name')
if (nameField.value == 'Galvanized') {
hideElem('#tab-label-pricing')
hideElem('#tab-pricing')
} else {
if (nameField.value == '') {
// create view
hideElem('#tab-label-galv_pricing')
hideElem('#tab-galv_pricing')
hideElem('#tab-label-pricing')
hideElem('#tab-pricing')
} else {
hideElem('#tab-label-galv_pricing')
hideElem('#tab-galv_pricing')
}
}
}
</script>
{% endblock %}

@ -48,18 +48,11 @@ from .models.project import (
)
class SnippetCreateView(CreateView):
class SelfRedirectEditView(EditView):
def get_success_url(self):
return self.get_edit_url()
class SnippetEditView(EditView):
def get_success_url(self):
return self.get_edit_url()
SnippetViewSet.create_view_class = SnippetCreateView
SnippetViewSet.edit_view_class = SnippetEditView
SnippetViewSet.list_per_page = 50
@ -67,6 +60,7 @@ class ProjectViewSet(SnippetViewSet):
model = Project
icon = 'helmet'
list_display = ['__str__', 'status', 'project_manager']
edit_view_class = SelfRedirectEditView
edit_handler = TabbedInterface([
ObjectList(
@ -235,6 +229,7 @@ class StairTreadStyleViewSet(ProductViewSet):
class LandingTreadStyleViewSet(ProductViewSet):
model = LandingTreadStyle
list_display = ['name', 'base_price', 'floor_price']
edit_handler = TabbedInterface([
ObjectList(
[
@ -246,7 +241,10 @@ class LandingTreadStyleViewSet(ProductViewSet):
),
ObjectList(
[
FieldPanel('base_price'),
MultiFieldPanel([
FieldPanel('base_price'),
FieldPanel('floor_price_adj'),
]),
],
heading='Pricing',
)
@ -255,13 +253,16 @@ class LandingTreadStyleViewSet(ProductViewSet):
class RailStyleViewSet(ProductViewSet):
model = RailStyle
list_display = ['name', 'base_price', 'add_price']
edit_handler = TabbedInterface([
ProductViewSet.frontend_panels_tab,
ObjectList(
[
FieldPanel('base_price'),
FieldPanel('add_price_adj'),
],
MultiFieldPanel([
FieldPanel('base_price'),
FieldPanel('add_price_adj'),
])
],
heading='Pricing',
)
])
@ -273,17 +274,45 @@ class MiscProductViewSet(ProductViewSet):
class FinishViewSet(SnippetViewSet):
model = Finish
create_template_name = 'core/finish_edit.html'
edit_template_name = 'core/finish_edit.html'
def get_edit_handler(self):
panels = TabbedInterface([
MultiFieldPanel(
[
FieldPanel('name'),
FieldPanel('sort_order'),
],
heading='Basic Info',
),
MultiFieldPanel(
[
FieldPanel('price_riser'),
FieldPanel('price_sqft'),
FieldPanel('price_ex_sr'),
FieldPanel('price_ex_wr'),
],
heading='Pricing',
),
Finish.galv_panel,
])
return panels.bind_to_model(self.model)
class SlabConstructionViewSet(SnippetViewSet):
model = SlabConstruction
panels = [
FieldPanel('name'),
FieldPanel('sort_order'),
MultiFieldPanel([
FieldPanel('name'),
FieldPanel('install_price'),
FieldPanel('uses_embeds'),
FieldPanel('uses_hangers'),
FieldPanel('sort_order'),
]),
]
class SlabConstructionViewSet(ProductViewSet):
model = SlabConstruction
class ProductViewSetGroup(SnippetViewSetGroup):
items = [
StairTreadStyleViewSet,
@ -297,7 +326,7 @@ class ProductViewSetGroup(SnippetViewSetGroup):
add_to_admin_menu = True
class BidEditView(SnippetEditView):
class BidEditView(SelfRedirectEditView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if self.get_form().is_valid():
@ -376,7 +405,7 @@ class BidViewSet(SnippetViewSet):
'revisions',
panels = [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('date', read_only=True),
TemplatePanel('core/takeoff_summary.html'),
]),
ReadOnlyInlinePanel(

@ -6,6 +6,7 @@ from wagtail.snippets.models import register_snippet
from wagtail.snippets.wagtail_hooks import SnippetsMenuItem
from wagtail.snippets.action_menu import ActionMenuItem
from .models.bid import Bid
from .models.frontend import LandingPageFeature, LandingPageImage
from .views import (
ProjectViewSetGroup,
ProductViewSetGroup,
@ -14,6 +15,8 @@ from .views import (
)
register_snippet(LandingPageFeature)
register_snippet(LandingPageImage)
register_snippet(ProjectViewSetGroup)
register_snippet(ProductViewSetGroup)
register_snippet(BidViewSetGroup)

@ -4,8 +4,9 @@ set -xe
python manage.py migrate --noinput --verbosity 0
python manage.py init_pages
python manage.py loaddata products.json
python manage.py import_legacy --first-project 1680 > import.log
python manage.py loaddata products.json salesreps.json
python manage.py loaddata salesreps.json
export DJANGO_SUPERUSER_EMAIL=$1
export DJANGO_SUPERUSER_PASSWORD=$2

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save