diff --git a/api/db/transaction.go b/api/db/transaction.go index 2ef9a38ddfd128ad1d6fc5336cce44580a89af63..e888c499d57cdd0ceb1c3283e0b233a54337d537 100644 --- a/api/db/transaction.go +++ b/api/db/transaction.go @@ -12,6 +12,10 @@ import ( "gorm.io/gorm/clause" ) +const ( + dateLayout = "2006-01-02" +) + type Transaction struct { gorm.Model MemberNum int `json:"-" gorm:"column:member"` @@ -37,7 +41,7 @@ type Topup struct { } type Purchase struct { - gorm.Model `json:"-"` + gorm.Model TransactionID uint `json:"-" gorm:"column:transaction"` ProductCode int `json:"code" gorm:"column:product"` Product Product `json:"product" gorm:"foreignKey:ProductCode;references:Code"` @@ -45,9 +49,11 @@ type Purchase struct { Amount int `json:"amount"` } -func (d *DB) ListTransactions(query map[string][]string) (transactions []Transaction, err error) { - const dateLayout = "2006-01-02" +func (d *DB) ListTransactions(num int, query map[string][]string) (transactions []Transaction, err error) { tx := d.transactionQuery() + if num != 0 { + tx = tx.Where("member = ?", num) + } for k, v := range query { switch k { case "start-date": @@ -66,6 +72,9 @@ func (d *DB) ListTransactions(query map[string][]string) (transactions []Transac } tx = tx.Where("date <= ?", date) case "member": + if num != 0 { + continue + } tx = tx.Where("member in ?", v) case "proxy": tx = tx.Where("proxy in ?", v) @@ -91,14 +100,7 @@ func (d *DB) ListTransactions(query map[string][]string) (transactions []Transac log.Printf("Unexpected transaction query: %s %v", k, v) } } - err = tx.Order("date desc"). - Find(&transactions).Error - return -} - -func (d *DB) TransactionByMember(num int) (transactions []Transaction, err error) { - err = d.transactionQuery(). - Where("member = ?", num). + err = tx.Group("transactions.id"). Order("date desc"). Find(&transactions).Error return diff --git a/api/transaction.go b/api/transaction.go index c70c31ce475392c2257f2cfc3a601f9d23d1edc4..5af75f7366b2394176d94a2ab71cd6dd961070a3 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -12,26 +12,7 @@ import ( ) func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) { - err := req.ParseForm() - if err != nil { - log.Printf("Can't parse the query: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - transactions, err := a.db.ListTransactions(req.Form) - if err != nil { - log.Printf("Can't list transactions: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(transactions) - if err != nil { - log.Printf("Can't encode transactions: %v", err) - w.WriteHeader(http.StatusInternalServerError) - } + a.getTransactionsByMember(0, w, req) } func (a *api) GetTransaction(num int, role string, w http.ResponseWriter, req *http.Request) { @@ -70,7 +51,14 @@ func (a *api) GetMemberTransactions(w http.ResponseWriter, req *http.Request) { } func (a *api) getTransactionsByMember(num int, w http.ResponseWriter, req *http.Request) { - transactions, err := a.db.TransactionByMember(num) + err := req.ParseForm() + if err != nil { + log.Printf("Can't parse the query: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + transactions, err := a.db.ListTransactions(num, req.Form) if err != nil { log.Printf("Can't list transactions: %v", err) w.WriteHeader(http.StatusInternalServerError) diff --git a/src/Head.js b/src/Head.js index 9934e1fced3a060ba2615d6f9085baa35994dc8e..a90058afe40269f25bcf55ce0c9cc78987fd519b 100644 --- a/src/Head.js +++ b/src/Head.js @@ -32,29 +32,29 @@ function Head(props) { ); } - let productNav = ( - <LinkContainer to="/products"> - <Nav.Link>Productos</Nav.Link> - </LinkContainer> + const productNav = ( + <NavDropdown title="Almacen" id="almacen"> + <LinkContainer to="/products"> + <NavDropdown.Item>Productos</NavDropdown.Item> + </LinkContainer> + <LinkContainer to="/purchases"> + <NavDropdown.Item>Compras</NavDropdown.Item> + </LinkContainer> + {auth.role === "admin" && ( + <div> + <LinkContainer to="/inventary"> + <NavDropdown.Item>Entradas Almacén</NavDropdown.Item> + </LinkContainer> + <LinkContainer to="/supplier/add"> + <NavDropdown.Item>Alta Proveedores</NavDropdown.Item> + </LinkContainer> + <LinkContainer to="/product/add"> + <NavDropdown.Item>Alta Codigos</NavDropdown.Item> + </LinkContainer> + </div> + )} + </NavDropdown> ); - if (auth.role === "admin") { - productNav = ( - <NavDropdown title="Almacen" id="admin"> - <LinkContainer to="/products"> - <NavDropdown.Item>Productos</NavDropdown.Item> - </LinkContainer> - <LinkContainer to="/inventary"> - <NavDropdown.Item>Entradas Almacén</NavDropdown.Item> - </LinkContainer> - <LinkContainer to="/supplier/add"> - <NavDropdown.Item>Alta Proveedores</NavDropdown.Item> - </LinkContainer> - <LinkContainer to="/product/add"> - <NavDropdown.Item>Alta Codigos</NavDropdown.Item> - </LinkContainer> - </NavDropdown> - ); - } let orderNav = ( <LinkContainer to="/orders"> diff --git a/src/Panel.js b/src/Panel.js index 9a7e68fbd301c571b6f688d6da417146ba02e707..a97ac13bac5e4efe4ca704825aa8d361e1cca1aa 100644 --- a/src/Panel.js +++ b/src/Panel.js @@ -12,6 +12,7 @@ import ShowInventary from "./inventary/ShowInventary"; import CreateSupplier from "./inventary/CreateSupplier"; import Dashboard from "./Dashboard"; import OwnPassword from "./OwnPassword"; +import PurchaseList from "./purchase/PurchaseList"; import Purchase from "./purchase/Purchase"; import Topup from "./Topup"; import ShowTransaction from "./transaction/ShowTransaction"; @@ -82,6 +83,9 @@ function LogedPanel(props) { <Route path="/purchase"> <Purchase /> </Route> + <Route path="/purchases"> + <PurchaseList /> + </Route> <Route path="/topup/:num"> <Topup /> </Route> diff --git a/src/product/ProductPicker.js b/src/product/ProductPicker.js index ed723c9647cc6dc060de55e16157fa5b4cd2743b..79d815224941d87b489d99a81f980a5dcaac23bd 100644 --- a/src/product/ProductPicker.js +++ b/src/product/ProductPicker.js @@ -75,14 +75,9 @@ class ProductPicker extends React.Component { render() { const rows = this.props.picks.map((p, i) => { - return ( - <Form.Group key={p.code} as={Row}> - <Col> - <p>{p.code}</p> - </Col> - <Col xs={4}> - <p>{p.name}</p> - </Col> + let price; + if (!this.props.noPrice) { + price = ( <Col> {this.props.price ? ( <PriceEditor @@ -93,6 +88,17 @@ class ProductPicker extends React.Component { printMoney(p.price) + "€" )} </Col> + ); + } + return ( + <Form.Group key={p.code} as={Row}> + <Col> + <p>{p.code}</p> + </Col> + <Col xs={4}> + <p>{p.name}</p> + </Col> + {price} {(this.props.amount || this.props.stock) && ( <Col> <Form.Control @@ -123,9 +129,11 @@ class ProductPicker extends React.Component { <Col xs={4}> <h6>Nombre</h6> </Col> - <Col> - <h6>Precio</h6> - </Col> + {!this.props.noPrice && ( + <Col> + <h6>Precio</h6> + </Col> + )} {(this.props.amount || this.props.stock) && ( <Col> <h6>Cantidad</h6> @@ -152,7 +160,7 @@ class ProductPicker extends React.Component { selected={[]} /> </Col> - <Col xs={false} sm></Col> + {!this.props.noPrice && <Col xs={false} sm></Col>} {(this.props.amount || this.props.stock) && <Col xs={false} sm></Col>} <Col xs={false} sm={1}></Col> </Form.Group> diff --git a/src/purchase/PurchaseList.js b/src/purchase/PurchaseList.js new file mode 100644 index 0000000000000000000000000000000000000000..259f8d33d4bfc67b12f1a10a021dad98bac6eed3 --- /dev/null +++ b/src/purchase/PurchaseList.js @@ -0,0 +1,147 @@ +import React, { useState, useContext } from "react"; +import { Table, Form, Row, Col, Button } from "react-bootstrap"; +import { LinkContainer } from "react-router-bootstrap"; +import ProductPicker from "../product/ProductPicker"; +import MemberPicker from "../member/MemberPicker"; +import AuthContext from "../AuthContext"; +import Fetcher from "../Fetcher"; +import { date2string, daysAfterNow, printDate } from "../util"; + +function PurchaseList() { + const auth = useContext(AuthContext); + const [purchases, setPurchases] = useState([]); + const [startDate, setStartDate] = useState(date2string(daysAfterNow(-30))); + const [endDate, setEndDate] = useState(date2string(new Date())); + const [members, setMembers] = useState([]); + const [products, setProducts] = useState([]); + + const setTransactions = (transactions) => { + const pur = transactions + .map((t) => { + let purchases = t.purchase; + if (products.length !== 0) { + purchases = purchases.filter( + (p) => products.find((prod) => p.code === prod.code) !== undefined + ); + } + return purchases.map((p) => { + return { + ID: p.ID, + date: t.date, + product: p.product.name, + code: p.code, + amount: p.amount, + member: t.member.name, + num: t.member.num, + transaction: t.ID, + }; + }); + }) + .flat(); + setPurchases(pur); + }; + + const admin = auth.role === "admin"; + + let query = "type=purchase&start-date=" + startDate + "&end-date=" + endDate; + query += members.map((m) => "&member=" + m.num).join(""); + query += products.map((p) => "&product=" + p.code).join(""); + + const entries = purchases.map((p) => { + return ( + <LinkContainer to={"/transaction/" + p.transaction} key={p.ID}> + <tr> + <td>{printDate(p.date)}</td> + <td>{p.product}</td> + <td>{p.amount}</td> + {admin && <td>{p.member}</td>} + </tr> + </LinkContainer> + ); + }); + + const setMember = (m) => { + let newMembers = [...members]; + newMembers.push(m); + setMembers(newMembers); + }; + const delMember = (index) => { + let newMembers = [...members]; + newMembers.splice(index, 1); + setMembers(newMembers); + }; + let memberPicker; + if (admin) { + const rows = members.map((member, i) => ( + <Row key={member.num}> + <Col> + <p>{member.name}</p> + </Col> + <Col xs={1}> + <Button variant="danger" onClick={() => delMember(i)}> + - + </Button> + </Col> + </Row> + )); + memberPicker = ( + <Col xs={12} sm={6}> + {rows} + <MemberPicker onChange={setMember} /> + </Col> + ); + } + + return ( + <div> + <Form> + <Row> + <Form.Group as={Col}> + <Form.Label>Desde:</Form.Label> + <Form.Control + type="date" + value={startDate} + onChange={(e) => setStartDate(e.target.value)} + max={endDate} + /> + </Form.Group> + <Form.Group as={Col}> + <Form.Label>Hasta:</Form.Label> + <Form.Control + type="date" + value={endDate} + onChange={(e) => setEndDate(e.target.value)} + min={startDate} + max={Date.now()} + /> + </Form.Group> + </Row> + <Row> + <Col> + <ProductPicker picks={products} setPicks={setProducts} noPrice /> + </Col> + {memberPicker} + </Row> + </Form> + <br /> + <Fetcher + url={(admin ? "/api/transaction?" : "/api/transaction/mine?") + query} + onFetch={setTransactions} + > + <Table className="text-center" responsive> + <thead> + <tr> + <th>Fecha</th> + <th>Producto</th> + <th>Cantidad</th> + {admin && <th>Socia</th>} + </tr> + </thead> + <tbody>{entries}</tbody> + </Table> + </Fetcher> + </div> + ); +} + +export default PurchaseList; diff --git a/src/transaction/TransactionList.js b/src/transaction/TransactionList.js index 0367e412d35489bd8b028ae08d00bcc3db952795..b36d33e63283c29be15cc7d39ead60278e37ae88 100644 --- a/src/transaction/TransactionList.js +++ b/src/transaction/TransactionList.js @@ -34,7 +34,7 @@ function TransactionList() { if (proxy) { query += "&proxy=" + proxy.num; } - query += types.map((t) => "&type=" + engType[t]); + query += types.map((t) => "&type=" + engType[t]).join(""); const onTypeChange = (e) => { const newTypes = Array.from(e.target.selectedOptions, (o) => o.value);