# HG changeset patch # User Michiel Broek # Date 1557682878 -7200 # Node ID a14a31bfc73b5703b9e001f1c57ccf87d7c8ab01 # Parent 9f27c822b14d929a8c653bd6a2acbc9146826d14# Parent 487274c2e9dc78624cd91613e8d15ed1aa3359a7 Version 0.2.0 merged again. diff -r 9f27c822b14d -r a14a31bfc73b README.design --- a/README.design Sun May 12 19:24:34 2019 +0200 +++ b/README.design Sun May 12 19:41:18 2019 +0200 @@ -1,118 +1,7 @@ -Centrale daemon 'bmsd' regelt de dagelijkse berichten tussen MQTT en de -database. Om deze berichten betrouwbaar af te handelen is MQTT er tussen -gezet. -Alle sensoren en controllers communiceren uitsluitend via MQTT. - -Sensoren en controllers: - - 1. Vergisting controllers. - 2. Temperatuur loggen. - 3. Hergisting drukmeters. (druk + temp). - 4. Brouw controllers. - 5. Ispindel. - - -Stappen: - - 1. Importeren oude vergisting en brouw logs. DONE. - 2. Thermferm moet DLOG berichten gaan sturen, per wijziging en per 5 minuten. DONE. - 3. bmsd moet deze DLOG berichten verwerken. Versie 0.0.2. Bier producten in database. DONE. - 4. Versie 0.0.2 handmatig installeren op productie. DONE. - 5. Thermferm uitbreiden met vergisting stage. DONE. - 6. Brouw controller uitbreiden met MQTT. Niet, ESP32 wordt onstabiel. - 7. bmsd uitbreiden met brouw controller berichten. Niet, zie hierboven. - 8. bmsd productie platform upgraden, versie 0.0.3 DONE. - 9. bmsd recepten editor implementeren. DONE. - 10. bmsd uitbreiden met productie (brews) stappen, met hierin recepten. DONE. - 11. bmsd koppelen logs aan productie. DONE. - - - - -MQTT structuur volgens Sparkplug model. - -mbv1.0/fermenters/[NBIRTH,DBIRTH]// -mbv1.0/fermenters/DDATA/// - ---------------------------------------------------------------------------- - -namespace/group_id/message_type/edge_node_id/{device_id} - -namespace is de root, "mbv1.0" - -group_id oa: fermenters, brewcontrol, env_sensor - -message_type: NBIRTH - Birth certificate for MQTT EoN nodes. - NDEATH - Death certificate for MQTT EoN nodes. - NCMD - Node command message. - DBIRTH - Birth certificate for devices. - DDEATH - Death certificate for devices. - NDATA - Node data message. - DDATA - Device data message. - DLOG - Device data logging. - DCMD - Device command message. - STATE - Critical application state message. - -NCMD: reboot (application restart) - rebirth - -DCMD: fermenter state change + temperature settings. - fermenter profile install. - fermenter load product. - fermenter set stage - - -Product: code, uuid en naam. - Stage: Plan Wait Brew Primary Secondary Tertiary Package Carbonation Mature Taste Ready Closed - | | | | | | - | | | | | +------------- Log/rapport - | | | | +------------------------ rapport/etiketten - | +-------+---------+--------------------------------- Log/rapport - +--------------------------------------------------------- Log/rapport Main table: products. - In progress: overview. - In Progress: view charts. DONE fermenters, todo brewlogs. In Progress: view logs. - In Progress: update state. - In Progress: Tabbed screens. DONE. - Start new: some sort of wizzard like a new recipe. DONE. - Archive: select via name/code/date. Calendar: shows upcoming events. - Recipes can be copied to 'recipes' or 'brews', imported from 'recipes'/'products' or created manual. - Products recipes Beerxml import. - Recipes Beerxml import. - - ------------------------------------------------------------------------------ - - Formaat csv vergisting logs. - -Directory: www/logs/fermentation -Filenaam: product_code\ product_name.log - - 2014-11-15 18:39,BEER,PRIMARY,20.312,19.750,-1.500,20.5,18.6,18.8,35,12345,0,67890,Whatsup,Fermenter - | | | | | | | | | | | | | | | - 0 datetime + | | | | | | | | | | | | | | - 1 mode --------------+ | | | | | | | | | | | | | - 2 stage -------------------+ | | | | | | | | | | | | - 3 temp air -----------------------+ | | | | | | | | | | | - 4 temp beer -----------------------------+ | | | | | | | | | | - 5 temp chiller ---------------------------------+ | | | | | | | | | - 6 temp room -------------------------------------------+ | | | | | | | | - 7 setpoint low ---------------------------------------------+ | | | | | | | - 8 setpoint high -------------------------------------------------+ | | | | | | - 9 heater power ------------------------------------------------------+ | | | | | -10 heater usage ----------------------------------------------------------+ | | | | -11 cooler power --------------------------------------------------------------+ | | | -12 cooler usage ------------------------------------------------------------------+ | | -13 event --------------------------------------------------------------------------------+ | -14 fermenter uuid --------------------------------------------------------------------------------+ - -De oude logs zijn geimporteerd en geconverteerd. Nieuwe worden geschreven -door bmsd welke de log gegevens ontvangt via MQTT DLOG berichten. -Vanwege de snelheid van verwerken staan de logs niet in de database. -Kunnen we de loggegevens versturen in gzip formaat om bandbreedte te sparen? ----------------------------------------------------------------------------- @@ -122,10 +11,6 @@ Extra: -Gisten alcohol_tolerance veld toevoegen. Gisten diastaticus bit toevoegen. -Waarschuwing voor te zwaar voor de gist. -Waarschuwing voor overschrijden moutstort. Gist typen: kveik en brett? Apart of niet. - diff -r 9f27c822b14d -r a14a31bfc73b config.status diff -r 9f27c822b14d -r a14a31bfc73b configure diff -r 9f27c822b14d -r a14a31bfc73b configure.ac diff -r 9f27c822b14d -r a14a31bfc73b doc/Makefile --- a/doc/Makefile Sun May 12 19:24:34 2019 +0200 +++ b/doc/Makefile Sun May 12 19:41:18 2019 +0200 @@ -3,7 +3,8 @@ include ../Makefile.global -SRC = bms.sgml bms-ch1.sgml bms-ch2.sgml bms-ch3.sgml +SRC = bms.sgml bms-ch1.sgml bms-ch2.sgml bms-ch3.sgml bms-ch4.sgml \ + bms-ch5.sgml bms-ch6.sgml bms-ch7.sgml bms-ch8.sgml HTML = bms.html PDF = bms.pdf OTHER = Makefile docbook-utils.dsl diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch3.sgml --- a/doc/bms-ch3.sgml Sun May 12 19:24:34 2019 +0200 +++ b/doc/bms-ch3.sgml Sun May 12 19:41:18 2019 +0200 @@ -2,315 +2,10 @@ vim:syntax=docbksgml --> - -Protocollen. + +Instellingen. -De netwerk protocollen. +Instellingen tekst. - -MQTT topic formaat. -De topics zijn als volgt gedefinieerd: - -mbv1.0/group_id/message_type/edge_node/device_id - - - -group_id geeft het type apparaat aan zoals fermenters -en brewcontrol. -message_type geeft het bericht type aan zoals -NBIRTH, DDATA. -edge_node is de hostnaam van de node die het bericht stuurt. Dit is -de naam zonder domain toevoeging. -device_id is de verkorte naam van het apparaat module waarvan dit -bericht komt zoals de naam van een vergisting controller. Dit is niet aanwezig met NODE berichten. - - - -De volgende group_id namen zijn gedefinieerd: - - -brewery is voor de bms applicatie zelf. Nog uitwerken. -fermenters is voor vergisting controllers. -brewcontrol is een brouw controller. Deze controller kan een deel -of geheel brouwproces uitvoeren. -pressure is een drukmeter om bijvoorbeeld hergisting op de fles -te monitoren. - - -De volgende message_type namen zijn gedefinieerd: - -NBIRTH geeft aan wanneer een node opstart en met het netwerk -verbonden is. Dit is een zogenaamd persistent bericht, het blijft voor nieuwe -MQTT clients altijd zichtbaar. Bij het starten van een node wordt er een payload -verzonden, zie het payload formaat voor een node. Als een node afsluit wordt juist geen -payload verzonden zodat het bericht verdwijnt. -NDATA wordt verstuurd als er veranderingen zijn voor de node, -maar ook iedere vijf minuten om aan te geven dat de node nog "levend" en aanwezig -is. -NDEATH wordt verstuurd als een node offline gaat. Maar het kan ook -ontvangen worden als de MQTT verbinding verbroken wordt met een node, het NDEATH -bericht is ook het `last will' bericht van een node. Maar als het echt fout gaat -dan zal er mogenlijk nooit een NDEATH bericht gezien worden. -NCMD is een commando bestemd voor een node. -Dit kan bijvoorbeeld een reboot commando zijn. -DBIRTH is een of meer berichten van een apparaat wat online komt -en ingeschakeld is. Een apparaat is een deel van een node. -Bij het opstarten van de node is er geen device_id -omdat alle apparaten is een keer verstuurd worden. Indien er later een enkel apparaat -ingeschakeld wordt dan is er wel een geldige device_id aanwezig. -Hier ook weer, er is een payload bij opstarten en geen payload bij afsluiten om het -persistente bericht goed te houden. -DDATA heeft altijd een payload, maar deze hoeft niet volledig te zijn, -enkel de gewijzigde data moet in het bericht zitten. -DDEATH wordt verstuurd als een node offline gaat, of als het apparaat -uitgeschakelt wordt. -DLOG is een data log. Hier bestaat de payload uit gegevens die de -bms applicatie in de database zet. -DCMD is een commando voor een apparaat wat op een node geinstalleerd is. -Dit zullen voornamelijk instellingen voor dat enkele apparaat zijn. - - - - - - -Netwerk payload formaat voor een node - -De payload zoals die door een node verstuurd wordt. Het wordt in json formaat -verzonder zonder extra spaties en opmaak zoals hieronder is te zien. Het timestamp -is de unix tijd sinds 1 januari 1970. Het `seq' nummer wordt met ieder bericht met 1 -verhoogd. - - -{ - "timestamp": 1532201089, - "seq": 0, - "metric": { - "uuid": "b508f01c-1f82-4e8b-b0d2-d88ecfb53031", - "properties": { - "hardwaremake": "Raspberry", - "hardwaremodel": "Unknown", - "os": "Linux", - "os_version": "4.1.19+", - "FW": "0.8.2" - }, - "THB": { - "temperature": 20.0, - "humidity": 50.0, - "barometer": 1002 - }, - "GPS": { - "latitude": 1.2345, - "longitude": 2.3456, - "altitude": 20 - }, - "net": { - "address": "10.126.151.11", - "ifname": "eth0", - "rssi": 0 - } - } -} - - - - -Netwerk kommando payload formaat voor nodes. -De volgende kommando's kunnen gestuurd worden naar nodes: - -{ - "timestamp":1532201089, - "metric": { - "Node Control/Reboot":true - } -} - -Dit commando reboot niet de computer maar de applicatie die op een -computer zoals een Raspberry Pi geinstalleerd is. Een uitzondering zijn de -controllers zoals Arduino's en andere eenvoudige systemen. - -{ - "timestamp":1532201089, - "metric": { - "Node Control/Rebirth":true - } -} - -Dit commando zorgt er voor dat alle NBIRTH en DBIRTH berichten opnieuw -verzonden worden alsof de computer net is opgestart. Dit kan nuttig zijn na -een herstart van de bms applicatie zelf zodat de juiste nodes informatie weer -beschikbaar is. - - - -Netwerk payload data formaat voor vergisting controllers - -Dit is het meest uitgebreide formaat wat getoond is. Indien er bijvoorbeeld geen -chiller aanwezig is, dan wordt eem `null' gestuurd in plaats van een json blok -met gegevens. De werkelijke uitvoering van de hardware en de configuratie daarvan -bepaald dus het uiteindelijke payload formaat. - - -{ - "uuid": "48c9ae27-3f58-41c9-ae4b-1d57b249c45a", - "alias": "unit1", - "product": { - "uuid": "1eb0c7bf-bf06-491c-a086-ac5478d521b9", - "code": "CB0001", - "name": "Hoppy Housebeer" - }, - "air": { - "address": "70d60411-3ec8-40ab-998a-81fead83025f", - "state": "OK", - "temperature": 21.562 - }, - "beer": { - "address": "8ec36f9d-f382-4e32-a47f-732642e1018d", - "state": "OK", - "temperature": 22.125 - }, - "chiller": { - "address": "e81265b8-07f7-4b22-96c1-6f55a4b66a83", - "state": "OK", - "temperature": 12.437 - }, - "heater": { - "address": "d2f2d6bc-4d12-4852-9462-95f4c2476034", - "state": 0, - "usage": 10710793 - }, - "cooler": { - "address": "a9f30140-812c-4ec1-9e98-3a9d47deff7c", - "state": 0, - "usage": 920504 - }, - "fan": { - "address": "ae9f9887-8209-4810-9f58-ddfb34ee142f", - "state": 100, - "usage": 62889739 - }, - "light": { - "address": "cc6353cf-9c97-41b9-b6cf-00cea312e478", - "state": 0, - "usage": 29647290 - }, - "door": { - "address": "ad8746d1-0549-485a-a215-41e5cdde9e75", - "state": 1 - }, - "psu": { - "address": "e1bb7182-883d-4977-a1c0-76e214072fc5", - "state": 1 - }, - "stage": "PRIMARY", - "mode": "BEER", - "setpoint": { - "low": 21.0, - "high": 21.0 - }, - "webcam": { - "url":"https://the.webcamserver.com:8090/?action=stream", - "light": 1 - }, - "alarm": 0, - "profile": { - "uuid": "c93ad1bb-0446-4788-9c43-83990c5f8b82", - "name": "Witbier methode Cellis", - "state": "OFF", - "percent": 0, - "inittemp": { - "low": 17.9, - "high": 18.1 - }, - "fridgemode": 0, - "steps": [ - { - "resttime": 2, - "steptime": 0, - "target": { - "low": 18.0, - "high": 18.0 - }, - "fridgemode": 0 - }, - { - "resttime": 0, - "steptime": 24, - "target": { - "low": 18.0, - "high": 22.0 - }, - "fridgemode": 0 - }, - { - "resttime": 48, - "steptime": 96, - "target": { - "low": 26.0, - "high": 26.0 - }, - "fridgemode": 0 - } - ] - } -} - -Temperature states can be: OK, MISSING or ERROR. -The general `mode' can be: OFF, NONE, FRIDGE, BEER or PROFILE. -The profile `state' can be: OFF, PAUSE, RUN, DONE or ABORT. - - - - -Netwerk payload log formaat voor vergisting controllers - -Dit is het meest uitgebreide formaat wat getoond is. Indien er bijvoorbeeld geen -chiller aanwezig is, dan wordt geen data hiervoor verstuurd. -De werkelijke uitvoering van de hardware en de configuratie daarvan -bepaald dus het uiteindelijke payload formaat. - - -{ - "timestamp": 1532201089, - "seq": 0, - "metric": { - "product": { - "uuid": "1eb0c7bf-bf06-491c-a086-ac5478d521b9", - "code": "CB0001", - "name": "Hoppy Housebeer" - }, - "stage": "PRIMARY", - "mode": "BEER", - "temperature": { - "air": 20.125, - "beer": 20.062, - "chiller": -3.000, - "room": 20.1 - }, - "setpoint": { - "low": 19.8, - "high": 20.1 - }, - "heater": { - "power": 100, - "usage": 1234 - }, - "cooler": { - "power": 0, - "usage": 27273 - }, - "fan": { - "power": 100, - "usage": 8273772 - }, - "sg": 1.023, - "event": "Something to mark", - "fermenter_uuid": "48c9ae27-3f58-41c9-ae4b-1d57b249c45a" - } -} - - - - diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch4.sgml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/bms-ch4.sgml Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,11 @@ + + + +Inventaris. + +Inventaris tekst. + + + diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch5.sgml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/bms-ch5.sgml Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,11 @@ + + + +Monitoren. + +Monitoren tekst. + + + diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch6.sgml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/bms-ch6.sgml Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,33 @@ + + + +Productie. + +Productie tekst. + + + +Productie fases. +Het productie proces is verdeeld in de volgende stappen: + + +Planning. +Wachten. +Brouwen. +Hoofdgisting. +Nagisting. +Lageren. +Verpakken. +Hergisten. +Rijpen. +Proeven. +Gereed. +Afgesloten. + + + + + + diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch7.sgml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/bms-ch7.sgml Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,11 @@ + + + +Recepten. + +Recepten tekst. + + + diff -r 9f27c822b14d -r a14a31bfc73b doc/bms-ch8.sgml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/bms-ch8.sgml Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,343 @@ + + + +Protocollen. + +De netwerk protocollen. + + + +MQTT topic formaat. +De topics zijn als volgt gedefinieerd: + +mbv1.0/group_id/message_type/edge_node/device_id + + + +group_id geeft het type apparaat aan zoals fermenters +en brewcontrol. +message_type geeft het bericht type aan zoals +NBIRTH, DDATA. +edge_node is de hostnaam van de node die het bericht stuurt. Dit is +de naam zonder domain toevoeging. +device_id is de verkorte naam van het apparaat module waarvan dit +bericht komt zoals de naam van een vergisting controller. Dit is niet aanwezig met NODE berichten. + + + +De volgende group_id namen zijn gedefinieerd: + + +brewery is voor de bms applicatie zelf. Nog uitwerken. +fermenters is voor vergisting controllers. +brewcontrol is een brouw controller. Deze controller kan een deel +of geheel brouwproces uitvoeren. +pressure is een drukmeter om bijvoorbeeld hergisting op de fles +te monitoren. + + +De volgende message_type namen zijn gedefinieerd: + +NBIRTH geeft aan wanneer een node opstart en met het netwerk +verbonden is. Dit is een zogenaamd persistent bericht, het blijft voor nieuwe +MQTT clients altijd zichtbaar. Bij het starten van een node wordt er een payload +verzonden, zie het payload formaat voor een node. Als een node afsluit wordt juist geen +payload verzonden zodat het bericht verdwijnt. +NDEATH wordt verstuurd als een node offline gaat. Maar het kan ook +ontvangen worden als de MQTT verbinding verbroken wordt met een node, het NDEATH +bericht is ook het `last will' bericht van een node. Maar als het echt fout gaat +dan zal er mogenlijk nooit een NDEATH bericht gezien worden. +NCMD is een commando bestemd voor een node. +Dit kan bijvoorbeeld een reboot commando zijn. +NDATA wordt verstuurd als er veranderingen zijn voor de node, +maar ook iedere vijf minuten om aan te geven dat de node nog "levend" en aanwezig +is. + +DBIRTH is een of meer berichten van een apparaat wat online komt +en ingeschakeld is. Een apparaat is een deel van een node. +Bij het opstarten van de node is er geen device_id +omdat alle apparaten is een keer verstuurd worden. Indien er later een enkel apparaat +ingeschakeld wordt dan is er wel een geldige device_id aanwezig. +Hier ook weer, er is een payload bij opstarten en geen payload bij afsluiten om het +persistente bericht goed te houden. +DDEATH wordt verstuurd als een node offline gaat, of als het apparaat +uitgeschakelt wordt. +DDATA heeft altijd een payload, maar deze hoeft niet volledig te zijn, +enkel de gewijzigde data moet in het bericht zitten. +DLOG is een data log. Hier bestaat de payload uit gegevens die de +bms applicatie in de database zet. +DCMD is een commando voor een apparaat wat op een node geinstalleerd is. +Dit zullen voornamelijk instellingen voor dat enkele apparaat zijn. + + + + + + +Netwerk payload formaat voor een node + +De payload zoals die door een node verstuurd wordt. Het wordt in json formaat +verzonder zonder extra spaties en opmaak zoals hieronder is te zien. Het timestamp +is de unix tijd sinds 1 januari 1970. Het `seq' nummer wordt met ieder bericht met 1 +verhoogd. + + +{ + "timestamp": 1532201089, + "seq": 0, + "metric": { + "uuid": "b508f01c-1f82-4e8b-b0d2-d88ecfb53031", + "properties": { + "hardwaremake": "Raspberry", + "hardwaremodel": "Unknown", + "os": "Linux", + "os_version": "4.1.19+", + "FW": "0.8.2" + }, + "THB": { + "temperature": 20.0, + "humidity": 50.0, + "barometer": 1002 + }, + "GPS": { + "latitude": 1.2345, + "longitude": 2.3456, + "altitude": 20 + }, + "net": { + "address": "10.126.151.11", + "ifname": "eth0", + "rssi": 0 + } + } +} + + + + +Netwerk kommando payload formaat voor nodes. +De volgende kommando's kunnen gestuurd worden naar nodes: + +{ + "timestamp":1532201089, + "metric": { + "Node Control/Reboot":true + } +} + +Dit commando reboot niet de computer maar de applicatie die op een +computer zoals een Raspberry Pi geinstalleerd is. Een uitzondering zijn de +controllers zoals Arduino's en andere eenvoudige systemen. + +{ + "timestamp":1532201089, + "metric": { + "Node Control/Rebirth":true + } +} + +Dit commando zorgt er voor dat alle NBIRTH en DBIRTH berichten opnieuw +verzonden worden alsof de computer net is opgestart. Dit kan nuttig zijn na +een herstart van de bms applicatie zelf zodat de juiste nodes informatie weer +beschikbaar is. + + + +Netwerk payload data formaat voor vergisting controllers + +Dit is het meest uitgebreide formaat wat getoond is. Indien er bijvoorbeeld geen +chiller aanwezig is, dan wordt een `null' gestuurd in plaats van een json blok +met gegevens. De werkelijke uitvoering van de hardware en de configuratie daarvan +bepaald dus het uiteindelijke payload formaat. + + +{ + "uuid": "48c9ae27-3f58-41c9-ae4b-1d57b249c45a", + "alias": "unit1", + "product": { + "uuid": "1eb0c7bf-bf06-491c-a086-ac5478d521b9", + "code": "CB0001", + "name": "Hoppy Housebeer" + }, + "air": { + "address": "70d60411-3ec8-40ab-998a-81fead83025f", + "state": "OK", + "temperature": 21.562 + }, + "beer": { + "address": "8ec36f9d-f382-4e32-a47f-732642e1018d", + "state": "OK", + "temperature": 22.125 + }, + "chiller": { + "address": "e81265b8-07f7-4b22-96c1-6f55a4b66a83", + "state": "OK", + "temperature": 12.437 + }, + "heater": { + "address": "d2f2d6bc-4d12-4852-9462-95f4c2476034", + "state": 0, + "usage": 10710793 + }, + "cooler": { + "address": "a9f30140-812c-4ec1-9e98-3a9d47deff7c", + "state": 0, + "usage": 920504 + }, + "fan": { + "address": "ae9f9887-8209-4810-9f58-ddfb34ee142f", + "state": 100, + "usage": 62889739 + }, + "light": { + "address": "cc6353cf-9c97-41b9-b6cf-00cea312e478", + "state": 0, + "usage": 29647290 + }, + "door": { + "address": "ad8746d1-0549-485a-a215-41e5cdde9e75", + "state": 1 + }, + "psu": { + "address": "e1bb7182-883d-4977-a1c0-76e214072fc5", + "state": 1 + }, + "stage": "PRIMARY", + "mode": "BEER", + "setpoint": { + "low": 21.0, + "high": 21.0 + }, + "webcam": { + "url":"https://the.webcamserver.com:8090/?action=stream", + "light": 1 + }, + "alarm": 0, + "profile": { + "uuid": "c93ad1bb-0446-4788-9c43-83990c5f8b82", + "name": "Witbier methode Cellis", + "state": "OFF", + "percent": 0, + "inittemp": { + "low": 17.9, + "high": 18.1 + }, + "fridgemode": 0, + "steps": [ + { + "resttime": 2, + "steptime": 0, + "target": { + "low": 18.0, + "high": 18.0 + }, + "fridgemode": 0 + }, + { + "resttime": 0, + "steptime": 24, + "target": { + "low": 18.0, + "high": 22.0 + }, + "fridgemode": 0 + }, + { + "resttime": 48, + "steptime": 96, + "target": { + "low": 26.0, + "high": 26.0 + }, + "fridgemode": 0 + } + ] + } +} + +Temperature states can be: OK, MISSING or ERROR. +The general `mode' can be: OFF, NONE, FRIDGE, BEER or PROFILE. +The profile `state' can be: OFF, PAUSE, RUN, DONE or ABORT. + + + + +Netwerk payload log formaat voor vergisting controllers + +Dit is het meest uitgebreide formaat wat getoond is. Indien er bijvoorbeeld geen +chiller aanwezig is, dan wordt geen data hiervoor verstuurd. +De werkelijke uitvoering van de hardware en de configuratie daarvan +bepaald dus het uiteindelijke payload formaat. + + +{ + "timestamp": 1532201089, + "seq": 0, + "metric": { + "product": { + "uuid": "1eb0c7bf-bf06-491c-a086-ac5478d521b9", + "code": "CB0001", + "name": "Hoppy Housebeer" + }, + "stage": "PRIMARY", + "mode": "BEER", + "temperature": { + "air": 20.125, + "beer": 20.062, + "chiller": -3.000, + "room": 20.1 + }, + "setpoint": { + "low": 19.8, + "high": 20.1 + }, + "heater": { + "power": 100, + "usage": 1234 + }, + "cooler": { + "power": 0, + "usage": 27273 + }, + "fan": { + "power": 100, + "usage": 8273772 + }, + "sg": 1.023, + "event": "Something to mark", + "fermenter_uuid": "48c9ae27-3f58-41c9-ae4b-1d57b249c45a" + } +} + + +De ontvangen vergisting log gegevens worden niet opgeslagen in de SQL database +maar in platte tekst bestanden. Hierdoor is de gelogde informatie sneller toegankelijk. +Ieder brouw product heeft zijn eigen bestand. +De bestanden staan in www/logs/fermentation/. +De bestandsnamen zijn product_code\ product_name.log. +Het interne formaat is: + + 2014-11-15 18:39,BEER,PRIMARY,20.312,19.750,-1.500,20.5,18.6,18.8,35,12345,0,67890,Whatsup,Fermenter + | | | | | | | | | | | | | | | + 0 datetime + | | | | | | | | | | | | | | + 1 werkwijze ---------+ | | | | | | | | | | | | | + 2 vergisting fase ---------+ | | | | | | | | | | | | + 3 temperatuur lucht --------------+ | | | | | | | | | | | + 4 temperatuur bier ----------------------+ | | | | | | | | | | + 5 temperatuur koeler ---------------------------+ | | | | | | | | | + 6 temperatuur ruimte ----------------------------------+ | | | | | | | | + 7 instelwaarde laag ----------------------------------------+ | | | | | | | + 8 instelwaarde hoog ---------------------------------------------+ | | | | | | + 9 verwarming vermogen -----------------------------------------------+ | | | | | +10 verwarming verbruik ---------------------------------------------------+ | | | | +11 koeler vermogen -----------------------------------------------------------+ | | | +12 koeler verbruik ---------------------------------------------------------------+ | | +13 gebeurtenis --------------------------------------------------------------------------+ | +14 vergister uuid --------------------------------------------------------------------------------+ + + + + + diff -r 9f27c822b14d -r a14a31bfc73b doc/bms.sgml --- a/doc/bms.sgml Sun May 12 19:24:34 2019 +0200 +++ b/doc/bms.sgml Sun May 12 19:41:18 2019 +0200 @@ -2,6 +2,11 @@ + + + + + ]> @@ -30,5 +35,10 @@ &chapter1; &chapter2; &chapter3; +&chapter4; +&chapter5; +&chapter6; +&chapter7; +&chapter8; diff -r 9f27c822b14d -r a14a31bfc73b www/Makefile --- a/www/Makefile Sun May 12 19:24:34 2019 +0200 +++ b/www/Makefile Sun May 12 19:41:18 2019 +0200 @@ -13,11 +13,11 @@ mon_brewer.php mon_fermenter.php mon_node.php \ prod_archive_code.php prod_archive_date.php prod_archive_name.php prod_beerxml.php \ prod_duplicate.php prod_edit.php prod_export.php prod_forum.php prod_impbrew.php \ - prod_inprod.php prod_new.php prod_print.php prod_reduce.php prod_torecipe.php \ + prod_inprod.php prod_new.php prod_print.php prod_torecipe.php \ profile_fermentation.php profile_mash.php profile_setup.php profile_styles.php \ profile_water.php \ - rec_beerxml.php rec_duplicate.php rec_edit.php rec_export.php rec_import.php \ - rec_main.php rec_new.php rec_print.php rec_toproduct.php \ + rec_beerxml.php rec_duplicate.php rec_edit.php rec_export.php rec_forum.php \ + rec_import.php rec_main.php rec_new.php rec_print.php rec_toproduct.php \ upl_brew.php upl_fermentables.php upl_hops.php upl_miscs.php upl_recipe.php \ upl_styles.php upl_yeasts.php version.php SUB = version.php.in images/* css/* jqwidgets/* jqwidgets/styles/* \ diff -r 9f27c822b14d -r a14a31bfc73b www/crontasks.php --- a/www/crontasks.php Sun May 12 19:24:34 2019 +0200 +++ b/www/crontasks.php Sun May 12 19:41:18 2019 +0200 @@ -12,28 +12,552 @@ mysqli_set_charset($connect, "utf8" ); syslog(LOG_NOTICE, "crontasks.php started"); + + +/* + * Upgrade inventory_reduced value from old boolean to tiny integer value. + */ +$query = "UPDATE products SET inventory_reduced=stage WHERE inventory_reduced = 1"; +$result = mysqli_query($connect, $query); +$changed = mysqli_affected_rows($connect); +if ($changed > 0) { + syslog(LOG_NOTICE, "Updated ".$changed." products to new inventory_reduced value"); +} + + + +/* + * Automatic reduce inventory depending on the production stage. + * Stage 3+, primary, reduce sugars(0-mash, 1-boil), hops(0-mash, 1-fwh, 2-boil, 3-aroma, 4-whirlpool), miscs(0-starter, 1-mash, 2-boil) + * Stage 4+, Secondary, reduce sugars(2-fermention), yeasts(0-Primary), miscs(3-primary) + * Stage 5+, Tertiary, reduce yeasts(1-Secondary) + * Stage 6+, packaged, reduce sugars(3-lagering), hops(5-dry-hop), yeasts(2-Tertiary), miscs(4-secondary) + * Stage 7+, carbonatiom, reduce sugars(4-bottle), yeasts(3-Bottle), miscs(5-bottling) + */ +$query = "SELECT * FROM products WHERE inventory_reduced < stage"; +$result = mysqli_query($connect, $query); +while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { + + $savethis = 0; + + /* + * If the brew is done, reduce the used ingredients. + */ + if (($row['stage'] >= 3) && ($row['inventory_reduced'] < 3)) { + syslog(LOG_NOTICE, "Reduce brew inventory from " . $row['code'] . " " . $row['name']); + + $fermentables = json_decode($row['json_fermentables'], true); + for ($i = 0; $i < count($fermentables); $i++) { + if ($fermentables[$i]['f_added'] <= 1) { // Mash, Boil + $sql2 = "UPDATE inventory_fermentables SET inventory = inventory - " . $fermentables[$i]['f_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory >= " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' with ".$fermentables[$i]['f_amount']." kg"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_fermentables SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory < " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' to 0 kg"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' failed"); + } + } + } + } + + $hops = json_decode($row['json_hops'], true); + for ($i = 0; $i < count($hops); $i++) { + if ($hops[$i]['h_useat'] <= 4) { // Mash, FWH, Boil, Flameout, Whirlpool + $sql2 = "UPDATE inventory_hops SET inventory = inventory - " . $hops[$i]['h_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); + $sql2 .= "' AND form=" . $hops[$i]['h_form']; + $sql2 .= " AND inventory >= " . $hops[$i]['h_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' with ".$hops[$i]['h_amount']." kg"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_hops SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); + $sql2 .= "' AND form=" . $hops[$i]['h_form']; + $sql2 .= " AND inventory < " . $hops[$i]['h_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' to 0 kg"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' failed"); + } + } + } + } + + $miscs = json_decode($row['json_miscs'], true); + for ($i = 0; $i < count($miscs); $i++) { + if ($miscs[$i]['m_use_use'] <= 2) { // Starter, Mash, Boil + $sql2 = "UPDATE inventory_miscs SET inventory = inventory - " . $miscs[$i]['m_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory >= " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' with ".$miscs[$i]['m_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_miscs SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory < " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce misc `".$miscs[$i]['m_name']."' failed"); + } + } + } + } + + if ($row['w1_name'] != '') { + $sql2 = "UPDATE inventory_waters SET inventory = inventory - ".$row['w1_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w1_name']); + $sql2 .= "' AND unlimited_stock=0 AND inventory >= ".$row['w1_amount']." LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced water `".$row['w1_name']."' with ".$row['w1_amount']." liter"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_waters SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w1_name']); + $sql2 .= "' AND unlimited_stock=0 AND inventory < ".$row['w1_amount']." LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced water `".$row['w1_name']."' to 0 liters"); + } else { + syslog(LOG_NOTICE, "Reduce water `".$row['w1_name']."' not reduced is maybe tapwater"); + } + } + } + if ($row['w2_name'] != '') { + $sql2 = "UPDATE inventory_waters SET inventory = inventory - ".$row['w2_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w2_name']); + $sql2 .= "' AND unlimited_stock=0 AND inventory >= ".$row['w2_amount']." LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced water `".$row['w2_name']."' with ".$row['w2_amount']." liter"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_waters SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w2_name']); + $sql2 .= "' AND unlimited_stock=0 AND inventory < ".$row['w2_amount']." LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced water `".$row['w2_name']."' to 0 liters"); + } else { + syslog(LOG_NOTICE, "Reduce water `".$row['w2_name']."' failed"); + } + } + } + + $row['inventory_reduced'] = '3'; + $savethis = 1; + } + + + /* + * After the Primary fermentation + */ + if (($row['stage'] >= 4) && ($row['inventory_reduced'] < 4)) { + syslog(LOG_NOTICE, "Reduce Primary inventory from " . $row['code'] . " " . $row['name']); + + $fermentables = json_decode($row['json_fermentables'], true); + for ($i = 0; $i < count($fermentables); $i++) { + if ($fermentables[$i]['f_added'] == 2) { // Fermentation + $sql2 = "UPDATE inventory_fermentables SET inventory = inventory - " . $fermentables[$i]['f_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory >= " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' with ".$fermentables[$i]['f_amount']." kg"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_fermentables SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory < " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' to 0 kg"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' failed"); + } + } + } + } + + $miscs = json_decode($row['json_miscs'], true); + for ($i = 0; $i < count($miscs); $i++) { + if ($miscs[$i]['m_use_use'] == 3) { // Fermentation + $sql2 = "UPDATE inventory_miscs SET inventory = inventory - " . $miscs[$i]['m_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory >= " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' with ".$miscs[$i]['m_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_miscs SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory < " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce misc `".$miscs[$i]['m_name']."' failed"); + } + } + } + } + + $yeasts = json_decode($row['json_yeasts'], true); + for ($i = 0; $i < count($yeasts); $i++) { + if ($yeasts[$i]['y_use'] == 0) { // Primary + $sql2 = "UPDATE inventory_yeasts SET inventory = inventory - " . $yeasts[$i]['y_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory >= " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' with ".$yeasts[$i]['y_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_yeasts SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory < " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' failed"); + } + } + } + } + + $row['inventory_reduced'] = '4'; + $savethis = 1; + } + + + /* + * After the Seconday fermentation + */ + if (($row['stage'] >= 5) && ($row['inventory_reduced'] < 5)) { + syslog(LOG_NOTICE, "Reduce Secondary inventory from " . $row['code'] . " " . $row['name']); + + $yeasts = json_decode($row['json_yeasts'], true); + for ($i = 0; $i < count($yeasts); $i++) { + if ($yeasts[$i]['y_use'] == 1) { // Secondary + $sql2 = "UPDATE inventory_yeasts SET inventory = inventory - " . $yeasts[$i]['y_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory >= " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' with ".$yeasts[$i]['y_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_yeasts SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory < " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' failed"); + } + } + } + } + $row['inventory_reduced'] = '5'; + $savethis = 1; + } + + + /* + * After the Tertiary fermentation + */ + if (($row['stage'] >= 6) && ($row['inventory_reduced'] < 6)) { + syslog(LOG_NOTICE, "Reduce Tertiary inventory from " . $row['code'] . " " . $row['name']); + + $fermentables = json_decode($row['json_fermentables'], true); + for ($i = 0; $i < count($fermentables); $i++) { + if ($fermentables[$i]['f_added'] == 3) { // Lagering + $sql2 = "UPDATE inventory_fermentables SET inventory = inventory - " . $fermentables[$i]['f_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory >= " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' with ".$fermentables[$i]['f_amount']." kg"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_fermentables SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); + $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); + $sql2 .= "' AND inventory < " . $fermentables[$i]['f_amount']; + $sql2 .= " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' to 0 kg"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' failed"); + } + } + } + } + + $hops = json_decode($row['json_hops'], true); + for ($i = 0; $i < count($hops); $i++) { + if ($hops[$i]['h_useat'] == 5) { // Dry hop + $sql2 = "UPDATE inventory_hops SET inventory = inventory - " . $hops[$i]['h_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); + $sql2 .= "' AND form=" . $hops[$i]['h_form']; + $sql2 .= " AND inventory >= " . $hops[$i]['h_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' with ".$hops[$i]['h_amount']." kg"); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_hops SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); + $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); + $sql2 .= "' AND form=" . $hops[$i]['h_form']; + $sql2 .= " AND inventory < " . $hops[$i]['h_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' to 0 kg"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' failed"); + } + } + } + } + + $yeasts = json_decode($row['json_yeasts'], true); + for ($i = 0; $i < count($yeasts); $i++) { + if ($yeasts[$i]['y_use'] == 2) { // Tertiary + $sql2 = "UPDATE inventory_yeasts SET inventory = inventory - " . $yeasts[$i]['y_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory >= " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' with ".$yeasts[$i]['y_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_yeasts SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory < " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' failed"); + } + } + } + } + + $miscs = json_decode($row['json_miscs'], true); + for ($i = 0; $i < count($miscs); $i++) { + if ($miscs[$i]['m_use_use'] == 4) { // Secondary or Tertiary + $sql2 = "UPDATE inventory_miscs SET inventory = inventory - " . $miscs[$i]['m_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory >= " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' with ".$miscs[$i]['m_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_miscs SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory < " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce misc `".$miscs[$i]['m_name']."' failed"); + } + } + } + } + + $row['inventory_reduced'] = '6'; + $savethis = 1; + } + + + /* + * After packaging + * reduce sugars(4-bottle), yeasts(3-Bottle), miscs(5-bottling) + */ + if (($row['stage'] >= 7) && ($row['inventory_reduced'] < 7)) { + syslog(LOG_NOTICE, "Reduce Packaging inventory from " . $row['code'] . " " . $row['name']); + + // Bottle sugar, how? + + $yeasts = json_decode($row['json_yeasts'], true); + for ($i = 0; $i < count($yeasts); $i++) { + if ($yeasts[$i]['y_use'] == 3) { // Bottle + $sql2 = "UPDATE inventory_yeasts SET inventory = inventory - " . $yeasts[$i]['y_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory >= " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' with ".$yeasts[$i]['y_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_yeasts SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); + $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); + $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); + $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; + $sql2 .= " AND inventory < " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' failed"); + } + } + } + } + + $miscs = json_decode($row['json_miscs'], true); + for ($i = 0; $i < count($miscs); $i++) { + if ($miscs[$i]['m_use_use'] == 5) { // Bottle + $sql2 = "UPDATE inventory_miscs SET inventory = inventory - " . $miscs[$i]['m_amount']; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory >= " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' with ".$miscs[$i]['m_amount']); + } else if ($ar == 0) { + $sql2 = "UPDATE inventory_miscs SET inventory = 0"; + $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); + $sql2 .= "' AND inventory < " . $miscs[$i]['m_amount'] . " LIMIT 1;"; + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar == 1) { + syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' to 0"); + } else if ($ar == 0) { + syslog(LOG_NOTICE, "Reduce misc `".$miscs[$i]['m_name']."' failed"); + } + } + } + } + + $row['inventory_reduced'] = $row['stage']; + $savethis = 1; + } + + /* + * Save only if something was reduced. + */ + if ($savethis == 1) { + $sql2 = "UPDATE products SET inventory_reduced=".$row['inventory_reduced']." WHERE uuid = '" . $row['uuid'] . "';"; +// syslog(LOG_NOTICE, $sql2); + $result2 = mysqli_query($connect, $sql2); + $ar = mysqli_affected_rows($connect); + if ($ar != 1) { + syslog(LOG_NOTICE, $sql2." error, affected rows: ".$ar); + } + } +} + + + +/* + * Update stages after packaging depending on the age. + */ $query = "UPDATE products SET stage=7 WHERE stage = 6 AND DATEDIFF(CURDATE(), package_date) > 0"; $result = mysqli_query($connect, $query); $changed = mysqli_affected_rows($connect); if ($changed > 0) { - syslog(LOG_NOTICE, "Updated ".$changed." products to stage 7"); + syslog(LOG_NOTICE, "Updated ".$changed." products to stage 7 (Carbonation)"); } $query = "UPDATE products SET stage=8 WHERE stage = 7 AND DATEDIFF(CURDATE(), package_date) > 13"; $result = mysqli_query($connect, $query); $changed = mysqli_affected_rows($connect); if ($changed > 0) { - syslog(LOG_NOTICE, "Updated ".$changed." products to stage 8"); + syslog(LOG_NOTICE, "Updated ".$changed." products to stage 8 (Mature)"); } $query = "UPDATE products SET stage=9 WHERE stage = 8 AND DATEDIFF(CURDATE(), package_date) > 41"; $result = mysqli_query($connect, $query); $changed = mysqli_affected_rows($connect); if ($changed > 0) { - syslog(LOG_NOTICE, "Updated ".$changed." products to stage 9"); + syslog(LOG_NOTICE, "Updated ".$changed." products to stage 9 (Taste)"); } +/* + * Check fementation logs. + */ $query = "SELECT record,code,name,log_brew,log_fermentation FROM products;"; $result = mysqli_query($connect, $query); while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { diff -r 9f27c822b14d -r a14a31bfc73b www/includes/db_product.php --- a/www/includes/db_product.php Sun May 12 19:24:34 2019 +0200 +++ b/www/includes/db_product.php Sun May 12 19:41:18 2019 +0200 @@ -688,29 +688,29 @@ $brew .= ',"sparge_acid_type":' . $row['sparge_acid_type']; $brew .= ',"sparge_acid_perc":' . floatval($row['sparge_acid_perc']); $brew .= ',"sparge_acid_amount":' . floatval($row['sparge_acid_amount']); - $brew .= ',"mash_ph":' . $row['mash_ph']; - $brew .= ',"mash_name":"' . $row['mash_name']; + $brew .= ',"mash_ph":' . floatval($row['mash_ph']); + $brew .= ',"mash_name":"' . str_replace($escapers, $replacements, $row['mash_name']); $brew .= '","calc_acid":' . $row['calc_acid']; $brew .= ',"w1_name":"' . str_replace($escapers, $replacements, $row['w1_name']); - $brew .= '","w1_amount":' . $row['w1_amount']; - $brew .= ',"w1_calcium":' . $row['w1_calcium']; - $brew .= ',"w1_sulfate":' . $row['w1_sulfate']; - $brew .= ',"w1_chloride":' . $row['w1_chloride']; - $brew .= ',"w1_sodium":' . $row['w1_sodium']; - $brew .= ',"w1_magnesium":' . $row['w1_magnesium']; - $brew .= ',"w1_total_alkalinity":' . $row['w1_total_alkalinity']; - $brew .= ',"w1_ph":' . $row['w1_ph']; - $brew .= ',"w1_cost":' . $row['w1_cost']; + $brew .= '","w1_amount":' . floatval($row['w1_amount']); + $brew .= ',"w1_calcium":' . floatval($row['w1_calcium']); + $brew .= ',"w1_sulfate":' . floatval($row['w1_sulfate']); + $brew .= ',"w1_chloride":' . floatval($row['w1_chloride']); + $brew .= ',"w1_sodium":' . floatval($row['w1_sodium']); + $brew .= ',"w1_magnesium":' . floatval($row['w1_magnesium']); + $brew .= ',"w1_total_alkalinity":' . floatval($row['w1_total_alkalinity']); + $brew .= ',"w1_ph":' . floatval($row['w1_ph']); + $brew .= ',"w1_cost":' . floatval($row['w1_cost']); $brew .= ',"w2_name":"' . str_replace($escapers, $replacements, $row['w2_name']); - $brew .= '","w2_amount":' . $row['w2_amount']; - $brew .= ',"w2_calcium":' . $row['w2_calcium']; - $brew .= ',"w2_sulfate":' . $row['w2_sulfate']; - $brew .= ',"w2_chloride":' . $row['w2_chloride']; - $brew .= ',"w2_sodium":' . $row['w2_sodium']; - $brew .= ',"w2_magnesium":' . $row['w2_magnesium']; - $brew .= ',"w2_total_alkalinity":' . $row['w2_total_alkalinity']; - $brew .= ',"w2_ph":' . $row['w2_ph']; - $brew .= ',"w2_cost":' . $row['w2_cost']; + $brew .= '","w2_amount":' . floatval($row['w2_amount']); + $brew .= ',"w2_calcium":' . floatval($row['w2_calcium']); + $brew .= ',"w2_sulfate":' . floatval($row['w2_sulfate']); + $brew .= ',"w2_chloride":' . floatval($row['w2_chloride']); + $brew .= ',"w2_sodium":' . floatval($row['w2_sodium']); + $brew .= ',"w2_magnesium":' . floatval($row['w2_magnesium']); + $brew .= ',"w2_total_alkalinity":' . floatval($row['w2_total_alkalinity']); + $brew .= ',"w2_ph":' . floatval($row['w2_ph']); + $brew .= ',"w2_cost":' . floatval($row['w2_cost']); $brew .= ',"wa_acid_name":' . $row['wa_acid_name']; $brew .= ',"wa_acid_perc":' . $row['wa_acid_perc']; $brew .= ',"wa_base_name":' . $row['wa_base_name']; @@ -784,6 +784,8 @@ for ($i = 0; $i < count($yeasts); $i++) { $yeasts[$i]['y_inventory'] = 0; // Not in stock $yeasts[$i]['y_avail'] = 0; // Ingredient not in db + if (! isset($yeasts[$i]['y_tolerance'])) + $yeasts[$i]['y_tolerance'] = 0; $sql2 = "SELECT inventory,tolerance FROM inventory_yeasts "; $sql2 .= "WHERE name='".str_replace($rescapers, $rreplacements, $yeasts[$i]['y_name'])."' AND"; $sql2 .= " form='".str_replace($rescapers, $rreplacements, $yeasts[$i]['y_form'])."' AND"; diff -r 9f27c822b14d -r a14a31bfc73b www/includes/db_recipes.php --- a/www/includes/db_recipes.php Sun May 12 19:24:34 2019 +0200 +++ b/www/includes/db_recipes.php Sun May 12 19:41:18 2019 +0200 @@ -32,7 +32,7 @@ $uuid = str_replace("\n", "", file_get_contents('/proc/sys/kernel/random/uuid')); $sql .= "uuid='" . $uuid; } - ($_POST['locked'] == 'true') ? $sql .= "', locked='1" : $sql .= "', locked='0"; + $sql .= "', locked='" . $_POST['locked']; $sql .= "', st_name='" . mysqli_real_escape_string($connect, $_POST['st_name']); $sql .= "', st_letter='" . mysqli_real_escape_string($connect, $_POST['st_letter']); $sql .= "', st_guide='" . mysqli_real_escape_string($connect, $_POST['st_guide']); @@ -75,7 +75,7 @@ $sql .= "', sparge_acid_amount='" . $_POST['sparge_acid_amount']; $sql .= "', mash_ph='" . $_POST['mash_ph']; $sql .= "', mash_name='" . $_POST['mash_name']; - ($_POST['calc_acid'] == 'true') ? $sql .= "', calc_acid='1" : $sql .= "', calc_acid='0"; + $sql .= "', calc_acid='" . $_POST['calc_acid']; if (isset($_POST['w1_name'])) { $sql .= "', w1_name='" . mysqli_real_escape_string($connect, $_POST['w1_name']); $sql .= "', w1_amount='" . $_POST['w1_amount']; @@ -103,7 +103,7 @@ $sql .= "', wa_acid_name='" . $_POST['wa_acid_name']; $sql .= "', wa_acid_perc='" . $_POST['wa_acid_perc']; $sql .= "', wa_base_name='" . $_POST['wa_base_name']; - syslog(LOG_NOTICE, $sql); + //syslog(LOG_NOTICE, $sql); $fermentables = '['; $comma = FALSE; @@ -202,6 +202,7 @@ $comma = FALSE; if (isset($_POST['yeasts'])) { $array = $_POST['yeasts']; +syslog(LOG_NOTICE, $_POST['yeasts']); foreach($array as $key => $item) { if ($comma) $yeasts .= ','; @@ -221,7 +222,7 @@ $yeast .= ',"y_inventory":' . $item['y_inventory']; $yeast .= ',"y_use":' . $item['y_use']; $yeast .= ',"y_cost":' . $item['y_cost'] . '}'; - //syslog(LOG_NOTICE, $yeast); + syslog(LOG_NOTICE, $yeast); $yeasts .= $yeast; } } @@ -247,7 +248,7 @@ $mash .= ',"ramp_time":' . $item['ramp_time']; $mash .= ',"end_temp":' . $item['end_temp'] . '}'; $mashs .= $mash; - syslog(LOG_NOTICE, $mash); + //syslog(LOG_NOTICE, $mash); } } $mashs .= ']'; @@ -303,7 +304,7 @@ } $comma = TRUE; $recipes .= '{"record":' . $row['record']; - $recipes .= ',"uuid":"' . $row['uuid']; + $recipes .= ',"uuid":"' . str_replace($escapers, $replacements, $row['uuid']); $recipes .= '","locked":' . $row['locked']; $recipes .= ',"st_guide":"' . str_replace($escapers, $replacements, $row['st_guide']); $recipes .= '","st_letter":"' . str_replace($escapers, $replacements, $row['st_letter']); @@ -338,38 +339,38 @@ $recipes .= ',"color_method":' . $row['color_method']; $recipes .= ',"est_ibu":' . floatval($row['est_ibu']); $recipes .= ',"ibu_method":' . $row['ibu_method']; - $recipes .= ',"sparge_temp":' . $row['sparge_temp']; - $recipes .= ',"sparge_ph":' . $row['sparge_ph']; - $recipes .= ',"sparge_volume":' . $row['sparge_volume']; + $recipes .= ',"sparge_temp":' . floatval($row['sparge_temp']); + $recipes .= ',"sparge_ph":' . floatval($row['sparge_ph']); + $recipes .= ',"sparge_volume":' . floatval($row['sparge_volume']); $recipes .= ',"sparge_source":"' . $row['sparge_source']; $recipes .= '","sparge_acid_type":' . $row['sparge_acid_type']; - $recipes .= ',"sparge_acid_perc":' . $row['sparge_acid_perc']; - $recipes .= ',"sparge_acid_amount":' . $row['sparge_acid_amount']; - $recipes .= ',"mash_ph":' . $row['mash_ph']; - $recipes .= ',"mash_name":"' . $row['mash_name']; + $recipes .= ',"sparge_acid_perc":' . floatval($row['sparge_acid_perc']); + $recipes .= ',"sparge_acid_amount":' . floatval($row['sparge_acid_amount']); + $recipes .= ',"mash_ph":' . floatval($row['mash_ph']); + $recipes .= ',"mash_name":"' . str_replace($escapers, $replacements, $row['mash_name']); $recipes .= '","calc_acid":' . $row['calc_acid']; $recipes .= ',"w1_name":"' . str_replace($escapers, $replacements, $row['w1_name']); - $recipes .= '","w1_amount":' . $row['w1_amount']; - $recipes .= ',"w1_calcium":' . $row['w1_calcium']; - $recipes .= ',"w1_sulfate":' . $row['w1_sulfate']; - $recipes .= ',"w1_chloride":' . $row['w1_chloride']; - $recipes .= ',"w1_sodium":' . $row['w1_sodium']; - $recipes .= ',"w1_magnesium":' . $row['w1_magnesium']; - $recipes .= ',"w1_total_alkalinity":' . $row['w1_total_alkalinity']; - $recipes .= ',"w1_ph":' . $row['w1_ph']; - $recipes .= ',"w1_cost":' . $row['w1_cost']; + $recipes .= '","w1_amount":' . floatval($row['w1_amount']); + $recipes .= ',"w1_calcium":' . floatval($row['w1_calcium']); + $recipes .= ',"w1_sulfate":' . floatval($row['w1_sulfate']); + $recipes .= ',"w1_chloride":' . floatval($row['w1_chloride']); + $recipes .= ',"w1_sodium":' . floatval($row['w1_sodium']); + $recipes .= ',"w1_magnesium":' . floatval($row['w1_magnesium']); + $recipes .= ',"w1_total_alkalinity":' . floatval($row['w1_total_alkalinity']); + $recipes .= ',"w1_ph":' . floatval($row['w1_ph']); + $recipes .= ',"w1_cost":' . floatval($row['w1_cost']); $recipes .= ',"w2_name":"' . str_replace($escapers, $replacements, $row['w2_name']); - $recipes .= '","w2_amount":' . $row['w2_amount']; - $recipes .= ',"w2_calcium":' . $row['w2_calcium']; - $recipes .= ',"w2_sulfate":' . $row['w2_sulfate']; - $recipes .= ',"w2_chloride":' . $row['w2_chloride']; - $recipes .= ',"w2_sodium":' . $row['w2_sodium']; - $recipes .= ',"w2_magnesium":' . $row['w2_magnesium']; - $recipes .= ',"w2_total_alkalinity":' . $row['w2_total_alkalinity']; - $recipes .= ',"w2_ph":' . $row['w2_ph']; - $recipes .= ',"w2_cost":' . $row['w2_cost']; + $recipes .= '","w2_amount":' . floatval($row['w2_amount']); + $recipes .= ',"w2_calcium":' . floatval($row['w2_calcium']); + $recipes .= ',"w2_sulfate":' . floatval($row['w2_sulfate']); + $recipes .= ',"w2_chloride":' . floatval($row['w2_chloride']); + $recipes .= ',"w2_sodium":' . floatval($row['w2_sodium']); + $recipes .= ',"w2_magnesium":' . floatval($row['w2_magnesium']); + $recipes .= ',"w2_total_alkalinity":' . floatval($row['w2_total_alkalinity']); + $recipes .= ',"w2_ph":' . floatval($row['w2_ph']); + $recipes .= ',"w2_cost":' . floatval($row['w2_cost']); $recipes .= ',"wa_acid_name":' . $row['wa_acid_name']; - $recipes .= ',"wa_acid_perc":' . $row['wa_acid_perc']; + $recipes .= ',"wa_acid_perc":' . floatval($row['wa_acid_perc']); $recipes .= ',"wa_base_name":' . $row['wa_base_name']; if (isset($_GET['record'])) { // Append stock information. @@ -429,6 +430,8 @@ for ($i = 0; $i < count($yeasts); $i++) { $yeasts[$i]['y_inventory'] = 0; // Not in stock $yeasts[$i]['y_avail'] = 0; // Ingredient not in db + if (! isset($yeasts[$i]['y_tolerance'])) + $yeasts[$i]['y_tolerance'] = 0; $sql2 = "SELECT inventory,tolerance FROM inventory_yeasts "; $sql2 .= "WHERE name='".str_replace($rescapers, $rreplacements, $yeasts[$i]['y_name'])."' AND"; $sql2 .= " form='".str_replace($rescapers, $rreplacements, $yeasts[$i]['y_form'])."' AND"; diff -r 9f27c822b14d -r a14a31bfc73b www/js/prod_edit.js --- a/www/js/prod_edit.js Sun May 12 19:24:34 2019 +0200 +++ b/www/js/prod_edit.js Sun May 12 19:41:18 2019 +0200 @@ -57,6 +57,12 @@ var pitchrate = 0.75; // Yeast pitch rate default var initcells = 0; // Initial yeast cell count + var ok_fermentables = 1; // Fermentables are in stock + var ok_hops = 1; // Hops are in stock + var ok_miscs = 1; // Miscs are in stock + var ok_yeasts = 1; // Yeasts are in stock + var ok_waters = 1; // Waters are in stock + var hop_flavour = 0; var hop_aroma = 0; var mash_infuse = 0; @@ -104,18 +110,31 @@ theme: theme }); + function calcSupplies() { + + if (dataRecord.inventory_reduced > 6) { + $("#ok_pmpt").hide(); + return; + } + if (ok_fermentables && ok_hops && ok_miscs && ok_yeasts && ok_waters) + $("#ok_supplies").html(""); + else + $("#ok_supplies").html(""); + } + /* * All calculations that depend on changes in the fermentables, * volumes and equipments. */ function calcFermentables() { - console.log("calcFermentables()"); var sugarsf = 0; // fermentable sugars mash + boil var sugarsm = 0; // fermentable sugars in mash psugar = 0; pcara = 0; mashkg = 0; + ok_fermentables = 1; // All is in stock. + ok_yeasts = 1; var vol = 0; // Volume sugars after boil var addedS = 0; // Added sugars after boil var addedmass = 0; // Added mass after boil @@ -170,7 +189,16 @@ vol += (x * sugardensity + (1 - x) * 1) * row.f_amount; } colort += row.f_amount * ebc_to_srm(row.f_color); + // Check supplies. + if ((((dataRecord.inventory_reduced <= 2) && (row.f_added <= 1)) || // Mash or boil + ((dataRecord.inventory_reduced <= 3) && (row.f_added == 2)) || // Primary + ((dataRecord.inventory_reduced <= 5) && (row.f_added == 3)) || // Secondary or Tertiary + ((dataRecord.inventory_reduced <= 6) && (row.f_added == 4))) && row.f_inventory < row.f_amount) { + ok_fermentables = 0; + } } + $("#mash_kg").val(mashkg); + console.log("calcFermentables() supplies:"+ok_fermentables); to_100 = my_100; if (to_100) { $("#wf_amount").jqxNumberInput({ width: 90, readOnly: true, spinButtons: false }); @@ -221,7 +249,6 @@ if (dataRecord.brew_fermenter_volume > 0) { var sug = sg_to_plato(ogx) * dataRecord.brew_fermenter_volume * ogx / 100; //kg of sugar in sug += addedS; //kg - //console.log("Contents ferm_vol:"+dataRecord.brew_fermenter_volume+" top:"+top+" vol:"+vol+" addedS:"+addedS+" addedmass:"+addedmass); if ((dataRecord.brew_fermenter_volume * ogx + addedmass) > 0) { var pt = 100 * sug / (dataRecord.brew_fermenter_volume * ogx + addedmass + top); @@ -234,7 +261,6 @@ var scolor = ebc_to_color(dataRecord.brew_fermenter_color); $("#bcolorf").show(); document.getElementById("bcolorf").style.background= scolor; - //console.log("OG in fermenter:"+dataRecord.brew_fermenter_sg+" color:"+dataRecord.brew_fermenter_color); } } else { // Negative volume @@ -256,7 +282,6 @@ // Progress bars pmalts = mashkg / dataRecord.eq_mash_max * 100; - //console.log("mash kg: "+mashkg+" max: "+dataRecord.eq_mash_max+" perc: "+pmalts); $("#perc_malts").jqxProgressBar('val', pmalts); $("#perc_sugars").jqxProgressBar('val', psugar); $("#perc_cara").jqxProgressBar('val', pcara); @@ -277,7 +302,15 @@ initcells += (parseFloat(row.y_cells) / 1000000) * parseFloat(row.y_amount); } // TODO: brett in secondary ?? + if ((((dataRecord.inventory_reduced <= 3) && (row.y_use == 0)) || // Primary + ((dataRecord.inventory_reduced <= 4) && (row.y_use == 1)) || // Secondary + ((dataRecord.inventory_reduced <= 5) && (row.y_use == 2)) || // Tertiary + ((dataRecord.inventory_reduced <= 6) && (row.y_use == 3))) && // Bottle + (row.y_inventory < row.y_amount)) { + ok_yeasts = 0; + } } + calcSupplies(); if (svg == 0) svg = 77; @@ -297,7 +330,6 @@ // Calculate the final svg if available use the real value. if ((dataRecord.stage >= 6) && (dataRecord.fg > 0.990) && (dataRecord.fg < dataRecord.brew_fermenter_sg)) { svg = 100 * (dataRecord.brew_fermenter_sg - dataRecord.fg) / (dataRecord.brew_fermenter_sg - 1); - //console.log("real svg:"+svg); } $("#yeast_cells").val(initcells); @@ -364,9 +396,7 @@ if (volume <= 0) volume = dataRecord.batch_size - dataRecord.eq_trub_chiller_loss; - //console.log("getNeededYeastCells f:"+f+" volume:"+volume+" plato:"+plato+" sg:"+sg); var result = pitchrate * volume * plato; - //console.log("getNeededYeastCells("+pitchrate+"): "+result+" billion cells"); return result; } @@ -412,6 +442,7 @@ if (!(rows = $('#hopGrid').jqxGrid('getrows'))) { return; } + ok_hops = 1; for (var i = 0; i < rows.length; i++) { var row = rows[i]; total_ibus += toIBU(row.h_useat, row.h_form, preboil_sg, parseFloat(dataRecord.batch_size), @@ -423,6 +454,10 @@ row.h_useat, parseFloat(row.h_amount)); hop_aroma += hopAromaContribution(parseFloat(row.h_time), parseFloat(dataRecord.batch_size), row.h_useat, parseFloat(row.h_amount)); + if ((((dataRecord.inventory_reduced <= 2) && (row.h_useat <= 4)) || // Mash, FW, Boil, Aroma, Whirlpool + ((dataRecord.inventory_reduced <= 6) && (row.h_useat == 5))) && // Dry-hop + (row.h_inventory < row.h_amount)) + ok_hops = 0; } total_ibus = Math.round(total_ibus * 10) / 10; ferm_ibus = Math.round(ferm_ibus * 10) / 10; @@ -432,7 +467,7 @@ hop_flavour = 100; if (hop_aroma > 100) hop_aroma = 100; - console.log("calcIBUs(): " + total_ibus + " flavour: " + hop_flavour + " aroma: " + hop_aroma+" fermenter:"+ferm_ibus); + console.log("calcIBUs(): " + total_ibus + " flavour: " + hop_flavour + " aroma: " + hop_aroma+" fermenter:"+ferm_ibus+" supplies:"+ok_hops); dataRecord.est_ibu = total_ibus; $('#est_ibu').val(total_ibus); $('#est_ibu2').val(total_ibus); @@ -440,6 +475,7 @@ $("#hop_aroma").jqxProgressBar('val', hop_aroma); $("#brew_fermenter_ibu").val(ferm_ibus); calcStage(); + calcSupplies(); }; /* @@ -728,7 +764,6 @@ var needed = getNeededYeastCells(); console.log("calcYeast() pitchrate:"+pitchrate+" start:"+initcells+" needed:"+needed); calcSteps(dataRecord.starter_type, initcells, needed); - //console.log("calcYeast() pitchrate:"+pitchrate+" needed:"+needed); $("#need_cells").val(needed); $("#r1_irate").html(""); @@ -814,6 +849,27 @@ } }; + function calcMiscs() { + + ok_miscs = 1; + var rowscount = $("#miscGrid").jqxGrid('getdatainformation').rowscount; + + if (rowscount == 0) + return; + + for (var i = 0; i < rowscount; i++) { + var row = $("#miscGrid").jqxGrid('getrowdata', i); + if ((((dataRecord.inventory_reduced <= 2) && (row.m_use_use <= 2)) || // Starter, Mash, Boil + ((dataRecord.inventory_reduced <= 3) && (row.m_use_use == 3)) || // Primary + ((dataRecord.inventory_reduced <= 5) && (row.m_use_use == 4)) || // Secondary, Teriary + ((dataRecord.inventory_reduced <= 6) && (row.m_use_use == 5))) && // Bottle + (row.m_inventory < row.m_amount)) { + ok_miscs = 0; + } + } + calcSupplies(); + }; + function adjustMiscs(factor) { console.log("adjustMiscs("+factor+")"); @@ -904,7 +960,6 @@ return; var c = sg_to_plato(est_mash_sg); var m = sg_to_plato(parseFloat($("#brew_mash_sg").jqxNumberInput('decimal'))); - //console.log("calcMashEfficiency() c "+ c + " m " + m + " in " + parseFloat($("#brew_mash_sg").jqxNumberInput('decimal'))); if (c > 0.5) $("#brew_mash_efficiency").val(100 * m / c); else @@ -929,7 +984,6 @@ result = Math.round((tot / m * 100) * 10) / 10; if (result < 0) result = 0; - //console.log("calcEfficiencyBeforeBoil(): "+result); $("#brew_preboil_efficiency").val(result); } @@ -955,7 +1009,6 @@ result = Math.round((tot / m * 100) * 10) / 10; if (result < 0) result = 0; - //console.log("calcEfficiencyAfterBoil(): "+result); dataRecord.brew_aboil_efficiency = result; $("#brew_aboil_efficiency").val(result); @@ -975,7 +1028,6 @@ } function setWaterAgent(name, amount) { - //console.log("setWaterAgent(" + name + ", " + amount + ")"); var rows = $('#miscGrid').jqxGrid('getrows'); if (amount == 0) { for (var i = 0; i < rows.length; i++) { @@ -1259,7 +1311,6 @@ // Einde noot. if ($("#wa_acid_name").val() < 0 || $("#wa_acid_name").val() > 3) { - console.log("fix wa_acid_name"); $("#wa_acid_name").val(0); dataRecord.wa_acid_name = 0; } @@ -1267,7 +1318,6 @@ last_acid = AcidTypeData[$("#wa_acid_name").val()].nl; if ($("#wa_base_name").val() < 0 || $("#wa_base_name").val() > 3) { - console.log("fix wa_base_name"); $("#wa_base_name").val(0); dataRecord.wa_base_name = 0; } @@ -1529,7 +1579,6 @@ else if (RA > piCLSO4_high) Res = 'hoog'; setRangeIndicator('cl_so4', Res); -// console.log("low: "+piCLSO4_low+" val: "+RA+" high: "+piCLSO4_high); $('#wb_calcium').val(Math.round(calcium * 10) / 10); $('#wb_magnesium').val(Math.round(magnesium * 10) / 10); @@ -1579,6 +1628,8 @@ setRangeIndicator("ph", "normaal"); } calcSparge(); + calcMiscs(); + calcSupplies(); } function calcSparge() { @@ -1605,7 +1656,6 @@ $("#sparge_source").val(0); } } - //console.log("calcSparge() target pH: "+TargetpH+" Source: "+Source_pH+" alkalinity: "+Source_alkalinity); // Step 1: Compute the mole fractions of carbonic (f1o), bicarbonate (f2o) and carbonate(f3o) at the water pH var r1 = Math.pow(10, Source_pH - 6.38); @@ -1784,7 +1834,6 @@ // This is the calculated difference in seconds var timeDifference = date1_unixtime - date2_unixtime; var timeDifferenceInDays = timeDifference / 60 / 60 / 24; - //console.log(date1+' '+date2+' days: '+timeDifferenceInDays); if (timeDifferenceInDays > 0) { // At least one day if (timeDifferenceInDays >= 42) // 6 weeks newstage = 9; // Ready to taste @@ -1899,16 +1948,12 @@ $("#primary_end_brix").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); $("#primary_end_date").jqxDateTimeInput({ disabled: true }); } - if (dataRecord.stage < 6) { // Not yet packaged - $("#inventory_reduced").jqxCheckBox({ disabled : true }); - } else { + if (dataRecord.stage >= 6) { // Packaged $("#secondary_temp").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); $("#secondary_end_date").jqxDateTimeInput({ disabled: true }); $("#tertiary_temp").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); $("#fg").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); $("#final_brix").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); - if ($('#inventory_reduced').jqxCheckBox('checked')) - $("#inventory_reduced").jqxCheckBox({ disabled : true }); $("#package_date").jqxDateTimeInput({ disabled: true }); $("#bottle_amount").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); $("#keg_amount").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 }); @@ -2854,7 +2899,6 @@ $("#birth").val(dataRecord.birth); $("#stage").val(StageData[dataRecord.stage].nl); $("#notes").val(dataRecord.notes); - $("#inventory_reduced").val(dataRecord.inventory_reduced); $("#locked").val(dataRecord.locked); $("#eq_name").val(dataRecord.eq_name); $("#eq_notes").val(dataRecord.eq_notes); @@ -3242,10 +3286,17 @@ { text: 'Voorraad Kg', datafield: 'f_inventory', width: 120, align: 'right', cellsrenderer: function (row, columnfield, value, defaulthtml, columnproperties, rowdata) { var color = '#ffffff'; - if (value < rowdata.f_amount) - color = '#ff4040'; - return '' +fermentableAdapter.formatNumber(value, "f3") + ''; + if (((dataRecord.inventory_reduced <= 2) && (rowdata.f_added <= 1)) || // Mash or boil + ((dataRecord.inventory_reduced <= 3) && (rowdata.f_added == 2)) || // Primary + ((dataRecord.inventory_reduced <= 5) && (rowdata.f_added == 3)) || // Secondary or Tertiary + ((dataRecord.inventory_reduced <= 6) && (rowdata.f_added == 4))) { // Bottle + if (value < rowdata.f_amount) + color = '#ff4040'; + return '' +fermentableAdapter.formatNumber(value, "f3") + ''; + } else { + return ''; + } } }, { text: 'Procent', datafield: 'f_percentage', width: 90, align: 'right', @@ -3446,13 +3497,18 @@ }, { text: 'Voorraad', datafield: 'h_inventory', width: 110, align: 'right', cellsrenderer: function (index, datafield, value, defaultvalue, column, rowdata) { - var color = '#ffffff'; - if (value < rowdata.h_amount) - color = '#ff4040'; - var amount = dataAdapter.formatNumber(value, "f1") + ' kg'; - if (value < 1) - amount = dataAdapter.formatNumber(value * 1000, "f1") + ' gr'; - return '' + amount + ''; + if (((dataRecord.inventory_reduced <= 2) && (rowdata.h_useat <= 4)) || // Mash, FW, Boil, Aroma, Whirlpool + ((dataRecord.inventory_reduced <= 6) && (rowdata.h_useat == 5))) { // Dry hop + var color = '#ffffff'; + if (value < rowdata.h_amount) + color = '#ff4040'; + var amount = dataAdapter.formatNumber(value, "f1") + ' kg'; + if (value < 1) + amount = dataAdapter.formatNumber(value * 1000, "f1") + ' gr'; + return ''+amount+''; + } else { + return ''; + } } }, { text: '', datafield: 'Edit', columntype: 'button', width: 100, align: 'center', cellsrenderer: function () { @@ -3630,6 +3686,7 @@ }); }, ready: function() { + calcMiscs(); $('#jqxTabs').jqxTabs('next'); }, columns: [ @@ -3663,12 +3720,19 @@ }, { text: 'Voorraad', datafield: 'm_inventory', width: 110, align: 'right', cellsrenderer: function (index, datafield, value, defaultvalue, column, rowdata) { - var vstr = rowdata.m_amount_is_weight ? "gr":"ml"; - var color = '#ffffff'; - if (value < rowdata.m_amount) - color = '#ff4040'; - var amount = dataAdapter.formatNumber(value * 1000,"f2")+" "+vstr; - return '' + amount + ''; + if (((dataRecord.inventory_reduced <= 2) && (rowdata.m_use_use <= 2)) || // Starter, Mash, Boil + ((dataRecord.inventory_reduced <= 3) && (rowdata.m_use_use == 3)) || // Primary + ((dataRecord.inventory_reduced <= 5) && (rowdata.m_use_use == 4)) || // Secondary, Teriary + ((dataRecord.inventory_reduced <= 6) && (rowdata.m_use_use == 5))) { // Bottle + var vstr = rowdata.m_amount_is_weight ? "gr":"ml"; + var color = '#ffffff'; + if (value < rowdata.m_amount) + color = '#ff4040'; + var amount = dataAdapter.formatNumber(value * 1000,"f2")+" "+vstr; + return ''+amount+''; + } else { + return ''; + } } }, { text: '', datafield: 'Edit', columntype: 'button', width: 100, align: 'center', cellsrenderer: function () { @@ -3852,15 +3916,22 @@ }, { text: 'Voorraad', datafield: 'y_inventory', width: 90, align: 'right', cellsrenderer: function (index, datafield, value, defaultvalue, column, rowdata) { - var color = '#ffffff'; - if (value < rowdata.y_amount) - color = '#ff4040'; - var amount = dataAdapter.formatNumber(value*1000, "f0")+" ml"; - if (rowdata.y_form == 0) // Liquid - amount = dataAdapter.formatNumber(value, "f0")+" pk"; - else if (rowdata.y_form == 1) // Dry - amount = dataAdapter.formatNumber(value*1000, "f1")+" gr"; - return '' + amount + ''; + if (((dataRecord.inventory_reduced <= 3) && (rowdata.y_use == 0)) || // Primary + ((dataRecord.inventory_reduced <= 4) && (rowdata.y_use == 1)) || // Secondary + ((dataRecord.inventory_reduced <= 5) && (rowdata.y_use == 2)) || // Tertiary + ((dataRecord.inventory_reduced <= 6) && (rowdata.y_use == 3))) { // Bottle + var color = '#ffffff'; + if (value < rowdata.y_amount) + color = '#ff4040'; + var amount = dataAdapter.formatNumber(value*1000, "f0")+" ml"; + if (rowdata.y_form == 0) // Liquid + amount = dataAdapter.formatNumber(value, "f0")+" pk"; + else if (rowdata.y_form == 1) // Dry + amount = dataAdapter.formatNumber(value*1000, "f1")+" gr"; + return ''+amount+''; + } else { + return ''; + } } }, { text: '', datafield: 'Edit', columntype: 'button', width: 90, align: 'center', cellsrenderer: function () { @@ -4032,29 +4103,6 @@ $("#birth").jqxInput({ theme: theme, width: 120, height: 23 }); $("#stage").jqxTooltip({ content: 'De productie fase van dit product.' }); $("#stage").jqxInput({ theme: theme, width: 100, height: 23 }); - $("#inventory_reduced").jqxCheckBox({ theme: theme, width: 120, height: 23 }); - $('#inventory_reduced').on('checked', function (event) { - if (dataRecord.inventory_reduced == 0) { - saveRecord(); - var data = "reduce=1&uuid="+dataRecord.uuid+"&record="+my_record; - var url = "prod_reduce.php"; - $.ajax({ - dataType: 'json', - url: url, - cache: false, - data: data, - type: "POST", - success: function (data, status, xhr) { - console.log("success"); - window.location.href = my_return; - }, - error: function(jqXHR, textStatus, errorThrown) { - console.log("error"); - window.location.href = my_return; - } - }); - } - }); $("#locked").jqxCheckBox({ theme: theme, width: 120, height: 23, disabled : true }); $('#locked').on('checked', function (event) { if (dataRecord.stage >= 10) { @@ -4208,6 +4256,8 @@ $("#est_color2").jqxNumberInput( Show0dec ); $("#est_og2").jqxTooltip({ content: 'Het geschatte begin SG van dit product.' }); $("#est_og2").jqxNumberInput( Show3dec ); + $("#mash_kg").jqxTooltip({ content: 'Het gewicht van alle mouten in de maisch.' }); + $("#mash_kg").jqxNumberInput( Show3dec ); $("#perc_malts").jqxProgressBar({ width: 300, height: 23, @@ -4622,6 +4672,7 @@ $("#MiscReady").jqxButton({ template: "success", width: '90px', theme: theme }); $("#MiscReady").click(function () { $("#miscGrid").jqxGrid('sortby', 'm_use_use', 'asc'); + calcMiscs(); }); $("#wm_name").jqxInput({ theme: theme, width: 320, height: 23 }); $("#wm_instock").jqxCheckBox({ theme: theme, height: 23 }); diff -r 9f27c822b14d -r a14a31bfc73b www/js/rec_edit.js --- a/www/js/rec_edit.js Sun May 12 19:24:34 2019 +0200 +++ b/www/js/rec_edit.js Sun May 12 19:41:18 2019 +0200 @@ -1200,11 +1200,11 @@ console.log("calc.init()"); $("#calc_acid").on('checked', function (event) { - dataRecord.calc_acid = true; + dataRecord.calc_acid = 1; calcWater(); }); $("#calc_acid").on('unchecked', function (event) { - dataRecord.calc_acid = false; + dataRecord.calc_acid = 0; calcWater(); }); $("#w1_name").jqxDropDownList('selectItem', dataRecord.w1_name); @@ -1384,9 +1384,11 @@ calcSparge(); }); $('#locked').on('checked', function (event) { + dataRecord.locked = 1; setReadonly(true); }); $('#locked').on('unchecked', function (event) { + dataRecord.locked = 0; setReadonly(false); }); // setReadonly(false); @@ -1442,7 +1444,7 @@ record: my_record, uuid: dataRecord.uuid, name: $("#name").val(), - locked: $("#locked").val(), + locked: dataRecord.locked, notes: $("#notes").val(), st_name: $('#st_name').val(), st_letter: $('#st_letter').val(), @@ -1484,7 +1486,7 @@ sparge_acid_type: $("#sparge_acid_type").val(), sparge_acid_perc: parseFloat($("#sparge_acid_perc").jqxNumberInput('decimal')), sparge_acid_amount: dataRecord.sparge_acid_amount, - calc_acid: $("#calc_acid").val(), + calc_acid: dataRecord.calc_acid, w1_name: $("#w1_name").val(), w1_amount: parseFloat($("#w1_amount").jqxNumberInput('decimal')), w1_calcium: parseFloat($("#w1_calcium").jqxNumberInput('decimal')), @@ -1541,7 +1543,7 @@ datafields: [ { name: 'record', type: 'number' }, { name: 'uuid', type: 'string' }, - { name: 'locked', type: 'bool' }, + { name: 'locked', type: 'int' }, { name: 'st_name', type: 'string' }, { name: 'st_letter', type: 'string' }, { name: 'st_guide', type: 'string' }, @@ -1584,7 +1586,7 @@ { name: 'sparge_acid_amount', type: 'float' }, { name: 'mash_ph', type: 'float' }, { name: 'mash_name', type: 'string' }, - { name: 'calc_acid', type: 'bool' }, + { name: 'calc_acid', type: 'int' }, { name: 'w1_name', type: 'string' }, { name: 'w1_amount', type: 'float' }, { name: 'w1_calcium', type: 'float' }, @@ -3734,14 +3736,7 @@ position: 'top' }); - $("#Print").jqxButton({ template: "info", width: '80px', theme: theme }); - $("#Print").bind('click', function () { - saveRecord(); - // Open print in a new tab. - var url="rec_print.php?record=" + my_record; - window.open(url); - }); - + // Buttons below $("#Export").jqxButton({ template: "info", width: '80px', theme: theme }); $("#Export").bind('click', function () { saveRecord(); diff -r 9f27c822b14d -r a14a31bfc73b www/js/rec_export.js --- a/www/js/rec_export.js Sun May 12 19:24:34 2019 +0200 +++ b/www/js/rec_export.js Sun May 12 19:41:18 2019 +0200 @@ -29,6 +29,8 @@ $("#jqxRadioButton1").jqxRadioButton({ theme: theme, width: 250, height: 23 }); $("#jqxRadioButton2").jqxRadioButton({ theme: theme, width: 250, height: 23 }); $("#jqxRadioButton3").jqxRadioButton({ theme: theme, width: 250, height: 23 }); + $("#jqxRadioButton4").jqxRadioButton({ theme: theme, width: 250, height: 23 }); + $("#jqxRadioButton5").jqxRadioButton({ theme: theme, width: 250, height: 23 }); $('#jqxRadioButton1').on('checked', function (event) { $('#Start').jqxButton({ disabled: false }); choice = 1; @@ -41,6 +43,14 @@ $('#Start').jqxButton({ disabled: false }); choice = 3; }); + $('#jqxRadioButton4').on('checked', function (event) { + $('#Start').jqxButton({ disabled: false }); + choice = 4; + }); + $('#jqxRadioButton5').on('checked', function (event) { + $('#Start').jqxButton({ disabled: false }); + choice = 5; + }); $('#Start').jqxButton({ template: "success", width: '100px', theme: theme, disabled: true }); $('#Start').click(function () { @@ -53,8 +63,14 @@ } else if (choice == 3) { var url="rec_toproduct.php?record=" + my_record; window.open(url); - } - // Return to the original product - window.location.href = my_return + "?record=" + my_record; + } else if (choice == 4) { + var url="rec_print.php?record=" + my_record; + window.open(url); + } else if (choice == 5) { + var url="rec_forum.php?record=" + my_record; + window.open(url); + } + // Return to the original recipe + window.location.href = "rec_edit.php?record=" + my_record + "&return=" + my_return; }); }); diff -r 9f27c822b14d -r a14a31bfc73b www/prod_edit.php --- a/www/prod_edit.php Sun May 12 19:24:34 2019 +0200 +++ b/www/prod_edit.php Sun May 12 19:41:18 2019 +0200 @@ -45,8 +45,8 @@
Brouwzaal rendement:
- Ingredienten afgeboekt: -
+ Ingredienten aanwezig: +
Brouw volume: @@ -203,7 +203,8 @@
- + Maisch KG: +
Percentage cara:
diff -r 9f27c822b14d -r a14a31bfc73b www/prod_forum.php --- a/www/prod_forum.php Sun May 12 19:24:34 2019 +0200 +++ b/www/prod_forum.php Sun May 12 19:41:18 2019 +0200 @@ -150,8 +150,8 @@ } echo PHP_EOL . PHP_EOL; -echo "Specerij, kruid, brouwzout etc Type grondstof Gebruik tijdens Hoeveel" . PHP_EOL; -echo "------------------------------ --------------- -------------------- ----------" . PHP_EOL; +echo "Specerij, kruid, brouwzout etc Type grondstof Gebruik tijdens Hoeveel" . PHP_EOL; +echo "------------------------------ --------------- -------------------- -----------" . PHP_EOL; $arr = json_decode($row['json_miscs'], true); foreach($arr as $item) { $amount = floatval($item['m_amount']) * 1000; @@ -163,9 +163,9 @@ $gebruik = sprintf("%s %d min",$miscuse[$use_use],$time); else $gebruik = $miscuse[$use_use]; - $hoeveel = sprintf("%.1f %s",$amount,$aiw ? "gr":"ml"); + $hoeveel = sprintf("%.2f %s",$amount,$aiw ? "gr":"ml"); - echo sprintf("%s %-15s %-20s %10s", formatstr($item['m_name'], 30), $misctype[$item['m_type']], $gebruik, $hoeveel) . PHP_EOL; + echo sprintf("%s %-15s %-20s %11s", formatstr($item['m_name'], 30), $misctype[$item['m_type']], $gebruik, $hoeveel) . PHP_EOL; } echo PHP_EOL . PHP_EOL; diff -r 9f27c822b14d -r a14a31bfc73b www/prod_print.php --- a/www/prod_print.php Sun May 12 19:24:34 2019 +0200 +++ b/www/prod_print.php Sun May 12 19:41:18 2019 +0200 @@ -373,7 +373,7 @@ else $gebruik = $miscuse[$use_use]; - $hoeveel = sprintf("%.1f %s",$amount,$aiw ? "gr":"ml"); + $hoeveel = sprintf("%.2f %s",$amount,$aiw ? "gr":"ml"); $this->Cell($vul,5,$name,0,0,'L',true); $this->Cell(30,5,$misctype[$type],0,0,'L',true); $this->Cell(30,5,$gebruik,0,0,'L',true); diff -r 9f27c822b14d -r a14a31bfc73b www/prod_reduce.php --- a/www/prod_reduce.php Sun May 12 19:24:34 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,209 +0,0 @@ -= " . $fermentables[$i]['f_amount']; - $sql2 .= " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' with ".$fermentables[$i]['f_amount']." kg"); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_fermentables SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_name']); - $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_origin']); - $sql2 .= "' AND supplier='" . mysqli_real_escape_string($connect, $fermentables[$i]['f_supplier']); - $sql2 .= "' AND inventory < " . $fermentables[$i]['f_amount']; - $sql2 .= " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' to 0 kg"); - } else if ($ar == 0) { - syslog(LOG_NOTICE, "Reduced fermentable `".$fermentables[$i]['f_name']."' from `".$fermentables[$i]['f_supplier']."' failed"); - } -// syslog(LOG_NOTICE, "affected rows: ".$ar); - } - } - - $hops = json_decode($row['json_hops'], true); - for ($i = 0; $i < count($hops); $i++) { - - $sql2 = "UPDATE inventory_hops SET inventory = inventory - " . $hops[$i]['h_amount']; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); - $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); - $sql2 .= "' AND form=" . $hops[$i]['h_form']; - $sql2 .= " AND inventory >= " . $hops[$i]['h_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' with ".$hops[$i]['h_amount']." kg"); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_hops SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $hops[$i]['h_name']); - $sql2 .= "' AND origin='" . mysqli_real_escape_string($connect, $hops[$i]['h_origin']); - $sql2 .= "' AND form=" . $hops[$i]['h_form']; - $sql2 .= " AND inventory < " . $hops[$i]['h_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' to 0 kg"); - } else if ($ar == 0) { - syslog(LOG_NOTICE, "Reduced hop `".$hops[$i]['h_name']."' from `".$hops[$i]['h_origin']."' failed"); - } - } - } - - $miscs = json_decode($row['json_miscs'], true); - for ($i = 0; $i < count($miscs); $i++) { - - $sql2 = "UPDATE inventory_miscs SET inventory = inventory - " . $miscs[$i]['m_amount']; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); - $sql2 .= "' AND inventory >= " . $miscs[$i]['m_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' with ".$miscs[$i]['m_amount']); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_miscs SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $miscs[$i]['m_name']); - $sql2 .= "' AND inventory < " . $miscs[$i]['m_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' to 0"); - } else if ($ar == 0) { - syslog(LOG_NOTICE, "Reduced misc `".$miscs[$i]['m_name']."' failed"); - } - } - } - - $yeasts = json_decode($row['json_yeasts'], true); - for ($i = 0; $i < count($yeasts); $i++) { - - $sql2 = "UPDATE inventory_yeasts SET inventory = inventory - " . $yeasts[$i]['y_amount']; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); - $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); - $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); - $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; - $sql2 .= " AND inventory >= " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' with ".$yeasts[$i]['y_amount']); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_yeasts SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_name']); - $sql2 .= "' AND laboratory='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_laboratory']); - $sql2 .= "' AND product_id='" . mysqli_real_escape_string($connect, $yeasts[$i]['y_product_id']); - $sql2 .= "' AND form=" . $yeasts[$i]['y_form']; - $sql2 .= " AND inventory < " . $yeasts[$i]['y_amount'] . " LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' to 0"); - } else if ($ar == 0) { - syslog(LOG_NOTICE, "Reduced yeast `".$yeasts[$i]['y_product_id'].' '.$yeasts[$i]['y_name']."' from `".$yeasts[$i]['y_laboratory']."' failed"); - } - } - } - - if ($row['w1_name'] != '') { - $sql2 = "UPDATE inventory_waters SET inventory = inventory - ".$row['w1_amount']; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w1_name']); - $sql2 .= "' AND unlimited_stock=0 AND inventory >= ".$row['w1_amount']." LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced water `".$row['w1_name']."' with ".$row['w1_amount']." liter"); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_waters SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w1_name']); - $sql2 .= "' AND unlimited_stock=0 AND inventory < ".$row['w1_amount']." LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced water `".$row['w1_name']."' to 0 liters"); - } else { - syslog(LOG_NOTICE, "Reduced water `".$row['w1_name']."' failed or tapwater"); - } - } - } - if ($row['w2_name'] != '') { - $sql2 = "UPDATE inventory_waters SET inventory = inventory - ".$row['w2_amount']; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w2_name']); - $sql2 .= "' AND unlimited_stock=0 AND inventory >= ".$row['w2_amount']." LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced water `".$row['w2_name']."' with ".$row['w2_amount']." liter"); - } else if ($ar == 0) { - $sql2 = "UPDATE inventory_waters SET inventory = 0"; - $sql2 .= " WHERE name='" . mysqli_real_escape_string($connect, $row['w2_name']); - $sql2 .= "' AND unlimited_stock=0 AND inventory < ".$row['w2_amount']." LIMIT 1;"; -// syslog(LOG_NOTICE, $sql2); - $result2 = mysqli_query($connect, $sql2); - $ar = mysqli_affected_rows($connect); - if ($ar == 1) { - syslog(LOG_NOTICE, "Reduced water `".$row['w2_name']."' to 0 liters"); - } - } - } - - syslog(LOG_NOTICE, "Finished reducing inventory from ". $row['code'].' '.$row['name']); - mysqli_free_result($result1); - - $sql1 = "UPDATE products SET inventory_reduced=1 WHERE uuid = '" . $_POST['uuid'] . "';"; - //syslog(LOG_NOTICE, $sql1); - $result1 = mysqli_query($connect, $sql1); - $ar = mysqli_affected_rows($connect); -} - -exit(0); diff -r 9f27c822b14d -r a14a31bfc73b www/rec_edit.php --- a/www/rec_edit.php Sun May 12 19:24:34 2019 +0200 +++ b/www/rec_edit.php Sun May 12 19:41:18 2019 +0200 @@ -94,11 +94,10 @@
- - +
diff -r 9f27c822b14d -r a14a31bfc73b www/rec_export.php --- a/www/rec_export.php Sun May 12 19:24:34 2019 +0200 +++ b/www/rec_export.php Sun May 12 19:41:18 2019 +0200 @@ -24,6 +24,14 @@ Copieer als brouw product:
+ + Print dit recept: +
+ + + Export voor forum: +
+   diff -r 9f27c822b14d -r a14a31bfc73b www/rec_forum.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/rec_forum.php Sun May 12 19:41:18 2019 +0200 @@ -0,0 +1,237 @@ + + + + + + BMS v<?php echo $my_version;?> - Export recipe + + + +
+':
+                                        $strout .= '>';
+                                        break;
+                        case '&':
+                                        $strout .= '&';
+                                        break;
+                        case '"':
+                                        $strout .= '"';
+                                        break;
+                        default:
+                                        $strout .= $strin[$i];
+        	}
+
+		if ($cnt >= $len)
+			break;
+        }
+
+	for ($i = $cnt; $i < $len; $i++)
+		$strout .= " ";
+        return $strout;
+}
+
+
+echo "BMS v" . $my_version . " - Datum export: " . $prdate . PHP_EOL;
+echo "----------------------------------------------------------" . PHP_EOL;
+itemline("Bier naam", $row['name']);
+itemline("Bier stijl", $row['st_name']);
+itemline("Recept type", $recipetype[$row['type']]);
+itemline("Batch grootte", $row['batch_size'].' liter');
+itemline("Kooktijd", $row['boil_time'].' minuten');
+itemline("Brouwzaal rendement", $row['efficiency'].' %');
+itemline("Geschatte begin densiteit", sprintf("%.3f",$row['est_og']).' SG/ '.sprintf("%.1f", sg_to_plato($row['est_og'])).'°P');
+itemline("Geschatte eind densiteit", sprintf("%.3f",$row['est_fg']).' SG/ '.sprintf("%.1f", sg_to_plato($row['est_fg'])).'°P');
+itemline("Geschat alcohol", sprintf("%.1f",$row['est_abv']).'% vol');
+itemline("Kleur (" . $colormethod[$row['color_method']] . ")", $row['est_color'] . ' EBC');
+itemline("Bitterheid (" . $ibumethod[$row['ibu_method']] . ")", $row['est_ibu'] . ' IBU');
+echo PHP_EOL . PHP_EOL;
+
+$sugarsm = 0;
+$grainabsorbtion = 0;
+$mashwater = 0;
+
+$arr = json_decode($row['json_fermentables'], true);
+echo "Mout, granen en suikers                  EBC Gewicht kg     % Gebruik tijdens" . PHP_EOL;
+echo "---------------------------------------- --- ---------- ----- ----------------" . PHP_EOL;
+foreach($arr as $item) {
+	$name = formatstr($item['f_name'] . " (" . $item['f_supplier'] . ")", 40);
+	$amount   = floatval($item['f_amount']);
+	$d = $amount * (floatval($item['f_yield']) / 100) * (1 - floatval($item['f_moisture']) / 100);
+        if ($item['f_added']  == 0) {
+        	$d = floatval($row['efficiency']) / 100 * $d;
+                $sugarsm += $d;
+		$grainabsorbtion += $item['f_amount'];
+        }
+	echo sprintf("%s %3.0f %10.3f %5.1f ", $name, floatval($item['f_color']), $amount, floatval($item['f_percentage']));
+	echo $added[$item['f_added']] . PHP_EOL;
+}
+$preboil_sg = estimate_sg($sugarsm, floatval($row['boil_size']));
+echo PHP_EOL . PHP_EOL;
+
+
+echo "Hop                                      Vorm      α %   IBU   Gram Toevoegen moment" . PHP_EOL;
+echo "---------------------------------------- ------- ----- ----- ------ --------------------" . PHP_EOL;
+$arr = json_decode($row['json_hops'], true);
+foreach($arr as $item) {
+	$name   = formatstr($item['h_name'] . " (" . $item['h_origin'] . ")", 40);
+	$amount = floatval($item['h_amount']) * 1000;
+	$time   = floatval($item['h_time']);
+        $alpha  = floatval($item['h_alpha']);
+	$ibu    = calc_IBU($item['h_useat'], $item['h_form'], $preboil_sg, floatval($row['batch_size']), $amount, $time, $alpha, $row['ibu_method']);
+	$moment = $hopuse[$item['h_useat']];
+        if ($item['h_useat'] == 2) {    // Boil
+        	$moment = "Kook ".$time." minuten";
+        }
+
+	echo sprintf("%s %-7s %5.1f %5.1f %6.1f %-20s", $name, $hopform[$item['h_form']], $alpha, $ibu, $amount, $moment);
+	echo PHP_EOL;
+}
+echo PHP_EOL . PHP_EOL;
+
+echo "Specerij, kruid, brouwzout etc Type grondstof  Gebruik tijdens          Hoeveel" . PHP_EOL;
+echo "------------------------------ --------------- -------------------- -----------" . PHP_EOL;
+$arr = json_decode($row['json_miscs'], true);
+foreach($arr as $item) {
+	$amount  = floatval($item['m_amount']) * 1000;
+	$aiw     = $item['m_amount_is_weight'];
+	$use_use = $item['m_use_use'];
+	$time    = floatval($item['m_time']);
+
+	if ($use_use == 2)      // Boil
+        	$gebruik = sprintf("%s %d min",$miscuse[$use_use],$time);
+        else
+        	$gebruik = $miscuse[$use_use];
+	$hoeveel = sprintf("%.2f %s",$amount,$aiw ? "gr":"ml");
+
+	echo sprintf("%s %-15s %-20s %11s", formatstr($item['m_name'], 30), $misctype[$item['m_type']], $gebruik, $hoeveel) . PHP_EOL;
+}
+echo PHP_EOL . PHP_EOL;
+
+echo "Gistlab en code      Omschrijving                   Gebruik      Vorm            Hoeveel" . PHP_EOL;
+echo "-------------------- ------------------------------ ------------ ------------ ----------" . PHP_EOL;
+$arr = json_decode($row['json_yeasts'], true);
+foreach($arr as $item) {
+	$name    = formatstr($item['y_name'], 30);
+	$product = formatstr($item['y_laboratory']." ".$item['y_product_id'], 20);
+
+	if ($item['y_form'] == 0)       // Liquid
+                $amount = sprintf("%.0f",floatval($item['y_amount']))." pak";
+        else if ($item['y_form'] == 1)  // Dry
+                $amount = sprintf("%.1f",floatval($item['y_amount'])*1000)." gr";
+        else
+                $amount = sprintf("%.0f",floatval($item['y_amount'])*1000)." ml";
+
+	echo sprintf("%s %s %-12s %-12s %10s", $product, $name, $yeastuse[$item['y_use']], $yeastform[$item['y_form']], $amount) . PHP_EOL;
+}
+if ($row['starter_enable'] && $row['prop1_volume']) {
+	$sv = 0;
+	for ($i = 1; $i < 5; $i++) {
+		$pv = "prop".$i."_volume";
+		if (floatval($row[$pv]) && (floatval($row[$pv]) > $sv)) {
+			$sv = floatval($row[$pv]);
+		}
+	}
+	echo PHP_EOL . "Maak een giststarter van " . sprintf("%.1f", $sv) . " liter." . PHP_EOL;
+}
+echo PHP_EOL . PHP_EOL;
+
+if ($row['w1_name'])
+	$mashwater += floatval($row['w1_amount']);
+if ($row['w2_name'])
+	$mashwater += floatval($row['w2_amount']);
+
+echo "Maisch stap          stap type              stap temp    rusten  opwarmen" . PHP_EOL;
+echo "-------------------- ---------------------- --------- --------- ---------" . PHP_EOL;
+$arr = json_decode($row['json_mashs'], true);
+foreach($arr as $item) {
+	
+	if ($item['step_type'] == 1) {
+		$stype = formatstr($steptype[$item['step_type']], 22);
+	} else {
+		$stype = formatstr($steptype[$item['step_type']].' '.sprintf("%.1f", $item['step_infuse_amount']).' liter', 22);
+	}
+	echo sprintf("%s %s %s %s %s", 
+		formatstr($item['step_name'], 20), $stype, sprintf("%6.1f °C", $item['step_temp']),
+		sprintf("%4.0f min.", $item['step_time']), sprintf("%4.0f min.", $item['ramp_time'])) . PHP_EOL;
+}
+echo PHP_EOL;
+if ($row['w1_name'] && $row['w2_name']) {
+	itemline("Maischwater 1", sprintf("%.1f",floatval($row['w1_amount'])).' liter water '.$row['w1_name']);
+	itemline("Maischwater 2", sprintf("%.1f",floatval($row['w2_amount'])).' liter water '.$row['w2_name']);
+} else {
+	itemline("Maischwater", sprintf("%.1f",floatval($row['w1_amount'])).' liter water '.$row['w1_name']);
+}
+itemline("Maischwater aanzuren tot pH", $row['mash_ph'].' pH');
+$spoelw = ($row['boil_size'] - $mashwater + $grainabsorbtion + $row['eq_lauter_deadspace']) * 1.03;     // A small heat correction
+itemline("Spoelwater geschat", sprintf("%.1f", $spoelw)." liter");
+itemline("Spoelwater temperatuur", $row['sparge_temp'].' °C');
+itemline("Spoelwater aanzuren tot pH", sprintf("%.1f",$row['sparge_ph']).' pH');
+echo PHP_EOL . PHP_EOL;
+
+
+?>
+  
+ +