[REF][MOV] documentation apocalypse
Prior to this commit, the Odoo documentation was mainly split between
two repositories: odoo/odoo/doc and odoo/documentation-user. Some bits
of documentation were also hosted elsewhere (e.g., wiki, upgrade, ...).
This was causing several problems among which:
- The theme, config, Makefile, and similar technical resources had to
be duplicated. This resulted in inconsistent layout, features, and
build environments from one documentation to another.
- Some pages did not fit either documentation as they were relevant
for both users and developers. Some were relevant to neither of the
two (e.g., DB management).
- Cross-doc references had to be absolute links and they broke often.
- Merging large image files in the developer documentation would bloat
the odoo/odoo repository. Some contributions had to be lightened to
avoid merging too many images (e.g., Odoo development tutorials).
- Long-time contributors to the user documentation were chilly about
going through the merging process of the developer documentation
because of the runbot, mergebot, `odoo-dev` repository, etc.
- Some contributors would look for the developer documentation in the
`odoo/documentation-user` repository.
- Community issues about the user documentation were submitted on the
`odoo/odoo` repository and vice-versa.
Merging all documentations in one repository will allow us to have one
place, one theme, one work process, and one set of tools (build
environment, ...) for all of the Odoo docs.
As this is a good opportunity to revamp the layout of the documentation,
a brand new theme replaces the old one. It features a new way to
navigate the documentation, centered on the idea of always letting the
reader know what is the context (enclosing section, child pages, page
structure ...) of the page they are reading. The previous theme would
quickly confuse readers as they navigated the documentation and followed
cross-application links.
The chance is also taken to get rid of all the technical dangling parts,
performance issues, and left-overs. Except for some page-specific JS
scripts, the Odoo theme Sphinx extension is re-written from scratch
based on the latest Sphinx release to benefit from the improvements and
ease future contributions.
task-2351938
task-2352371
task-2205684
task-2352544
Closes #945
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
eac5e9f865
commit
e3fee2cf46
@@ -0,0 +1,222 @@
|
||||
/* FIXME: hack to reverse hack in theme CSS */
|
||||
main.index .toctree-wrapper > .row:first-child > .col-md-3 {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.doc-aside {
|
||||
border: 1px solid #d5d5d5;
|
||||
background-color: #f8f8f8;
|
||||
padding: 0 !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
.doc-aside p{
|
||||
padding: .5rem;
|
||||
}
|
||||
.intro-list {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.intro-list p {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.related {
|
||||
background-color: hsl(317deg 16% 90%) !important;
|
||||
transition: .3s;
|
||||
}
|
||||
.secondary {
|
||||
background-color: hsl(180deg 67% 94%) !important;
|
||||
transition: .3s;
|
||||
}
|
||||
.highlight-op {
|
||||
background-color: hsl(317deg 16% 90%) !important;
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
label:hover,
|
||||
label:hover,
|
||||
.highlighter-list li:hover {
|
||||
background-color: hsl(0, 0%, 94%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*.accounts-table {
|
||||
font-size: 90%;
|
||||
}*/
|
||||
|
||||
.accounts-table dl {
|
||||
margin: 0;
|
||||
padding: .5rem 0;
|
||||
}
|
||||
|
||||
.accounts-table h4, .accounts-table h5 {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
padding: .5rem;
|
||||
|
||||
}
|
||||
|
||||
.accounts-table h4 {
|
||||
text-align: center;
|
||||
}
|
||||
/* table root */
|
||||
.accounts-table > div {
|
||||
display: flex;
|
||||
}
|
||||
/* P&L & Balance Sheet columns */
|
||||
.accounts-table > div > div {
|
||||
flex: 1;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.accounts-table > div > div:first-child {
|
||||
border-right: 1px solid #d5d5d5
|
||||
}
|
||||
|
||||
/* sections */
|
||||
.accounts-table > div > div div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.accounts-table > div > div div > h5 {
|
||||
order: 99;
|
||||
border-top: 1px solid #d5d5d5;
|
||||
border-bottom: 1px solid #d5d5d5;
|
||||
/*text-transform: uppercase;*/
|
||||
/*padding: 0.3em 0;*/
|
||||
/*border-image: linear-gradient(to bottom, transparent, rgb(221, 225, 231), transparent, rgb(221, 225, 231), transparent) 5 0 10 / 3px 0 3px;*/
|
||||
}
|
||||
.accounts-table > div > div div div > h5 {
|
||||
/* slightly smaller than normal 14px h5 size */
|
||||
font-size: 12.5px;
|
||||
/*border-image: linear-gradient(to bottom, transparent, rgb(221, 225, 231), transparent, rgb(221, 225, 231), transparent) 5 0 5;*/
|
||||
}
|
||||
.accounts-table dt {
|
||||
font-weight: 700;
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
.accounts-table dt span {
|
||||
font-style: italic;
|
||||
}
|
||||
.accounts-table dt span:last-child {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.values-table tr > * {
|
||||
text-align: right;
|
||||
}
|
||||
.values-table tr > :first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 3-column (thing, debit, credit) tables */
|
||||
/* 2nd and 3rd th & td of each row right-aligned and 1/4th width */
|
||||
.d-c-table tr > :nth-child(2),
|
||||
.d-c-table tr > :nth-child(3) {
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.accounts-table, .doc-aside .highlighter-target {
|
||||
/*font-size: 90%;*/
|
||||
/*color: #8d8d8d !important;*/
|
||||
}
|
||||
.doc-aside .highlighter-target th {
|
||||
font-weight: 400;
|
||||
/*font-size: 110%;*/
|
||||
}
|
||||
.accounts-table .related, .doc-aside .highlighter-target .related {
|
||||
/*background-color: #eee !important;*/
|
||||
color: #7A436B !important;
|
||||
|
||||
}
|
||||
.accounts-table .secondary, .doc-aside .highlighter-target .secondary {
|
||||
background-color: #eee !important;
|
||||
color: #7A436B !important;
|
||||
}
|
||||
|
||||
.chart-of-accounts .highlight-op,
|
||||
.valuation-chart .highlight-op {
|
||||
background-color: #030035;
|
||||
}
|
||||
|
||||
.chart-of-accounts .highlight-op,
|
||||
.valuation-chart-continental .highlight-op {
|
||||
background-color: #030035;
|
||||
}
|
||||
|
||||
.chart-of-accounts .highlight-op,
|
||||
.valuation-chart-anglo-saxon .highlight-op {
|
||||
background-color: #030035;
|
||||
}
|
||||
}
|
||||
.entries-listing {
|
||||
padding: .5rem;
|
||||
}
|
||||
.journal-entries .entries-listing p {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#reconciliation .reconciliation-example div.buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
#reconciliation .reconciliation-example div.buttons button {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
@keyframes reconcile {
|
||||
0% {
|
||||
opacity: 1;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
60% {
|
||||
background-color: hsl(317deg 16% 90%);
|
||||
}
|
||||
80% {
|
||||
opacity: 1;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.reconcile1 .invoice1, .reconcile1 .invoice1 td {
|
||||
animation: reconcile 2s;
|
||||
}
|
||||
.reconcile2 .invoice2, .reconcile2 .invoice2 td {
|
||||
animation: reconcile 2s;
|
||||
}
|
||||
|
||||
.invoice1.reconciled, .invoice2.reconciled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
blockquote.highlights, blockquote.highlights p{
|
||||
border: none;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*
|
||||
lists of alternatives
|
||||
*/
|
||||
.alternatives-controls label {
|
||||
display: block;
|
||||
}
|
||||
dl.alternatives > dt,
|
||||
dl.alternatives > dd {
|
||||
display: none;
|
||||
}
|
||||
dl.alternatives > dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
section#terms-and-conditions table td {
|
||||
/* make sure icons and links aren't wrapped */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
section#terms-and-conditions table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#terms-and-conditions table img.img-fluid {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
section#terms-and-conditions table td:nth-child(2),
|
||||
section#terms-and-conditions table th:nth-child(2) {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@import "style.css";
|
||||
|
||||
p.rubric {
|
||||
font-family: Lato, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
color: inherit;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section p.rubric {
|
||||
font-size: 30px;
|
||||
}
|
||||
.section .section p.rubric {
|
||||
font-size: 24px;
|
||||
}
|
||||
.section .section .section p.rubric {
|
||||
font-size: 18px;
|
||||
}
|
||||
.section .section .section .section p.rubric {
|
||||
font-size: 14px;
|
||||
}
|
||||
.section .section .section .section .section p.rubric {
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
External ID,Name,Parent Category/External ID
|
||||
a1,Expenses,product.product_category_all
|
||||
a2,Other Products,product.product_category_all
|
||||
a3,Sellable Products,product.product_category_all
|
||||
a4,Tables,a1
|
||||
a5,Seating furniture,a2
|
||||
|
@@ -0,0 +1,6 @@
|
||||
External ID,Name,Internal Reference,Category/External ID,Can be Expensed,Can be Purchased,Can be Sold,Sale Price,Cost,Supply Method,Product Type,Procurement Method
|
||||
a6,Aluminum Stool,ALS,a5,False,True,True,49.00,25.00,Buy,Stockable Product,Make to Stock
|
||||
a7,Chair,CHR,a5,False,True,True,89.00,40.00,Buy,Stockable Product,Make to Stock
|
||||
a8,Table,TBL,a4,False,True,True,169.00,100.00,Buy,Stockable Product,Make to Stock
|
||||
a9,Software Book Tutorial,SBT,a2,False,True,False,19.00,8.00,Buy,Consumable,Make to Stock
|
||||
a10,Fuel,FL,a1,True,False,False,0.30,0.25,Buy,Service,Make to Stock
|
||||
|
@@ -0,0 +1,155 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = off;
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
SET escape_string_warning = off;
|
||||
|
||||
SET search_path = public, pg_catalog;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- Name: companies; Type: TABLE; Schema: public; Owner: fp; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE companies (
|
||||
id integer NOT NULL,
|
||||
company_name character varying
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.companies OWNER TO fp;
|
||||
|
||||
--
|
||||
-- Name: companies_id_seq; Type: SEQUENCE; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
CREATE SEQUENCE companies_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.companies_id_seq OWNER TO fp;
|
||||
|
||||
--
|
||||
-- Name: companies_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
ALTER SEQUENCE companies_id_seq OWNED BY companies.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: companies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('companies_id_seq', 3, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: persons; Type: TABLE; Schema: public; Owner: fp; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE persons (
|
||||
id integer NOT NULL,
|
||||
company_id integer,
|
||||
person_name character varying
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.persons OWNER TO fp;
|
||||
|
||||
--
|
||||
-- Name: persons_id_seq; Type: SEQUENCE; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
CREATE SEQUENCE persons_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.persons_id_seq OWNER TO fp;
|
||||
|
||||
--
|
||||
-- Name: persons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
ALTER SEQUENCE persons_id_seq OWNED BY persons.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: persons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('persons_id_seq', 4, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY companies ALTER COLUMN id SET DEFAULT nextval('companies_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY persons ALTER COLUMN id SET DEFAULT nextval('persons_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: companies; Type: TABLE DATA; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
COPY companies (id, company_name) FROM stdin;
|
||||
1 Bigees
|
||||
2 Organi
|
||||
3 Boum
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: persons; Type: TABLE DATA; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
COPY persons (id, company_id, person_name) FROM stdin;
|
||||
1 1 Fabien
|
||||
2 1 Laurence
|
||||
3 2 Eric
|
||||
4 3 Ramsy
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Name: companies_pkey; Type: CONSTRAINT; Schema: public; Owner: fp; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY companies
|
||||
ADD CONSTRAINT companies_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: persons_company_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: fp
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY persons
|
||||
ADD CONSTRAINT persons_company_id_fkey FOREIGN KEY (company_id) REFERENCES companies(id);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Name,Reference,Tags,Customer,Street,City,Country
|
||||
Credit & Leasing,3,Services,True,Central Avenue 814,Johannesburg,South Africa
|
||||
Services & Finance,5,"Consultancy Services,IT Services",True,Grove Road 5,London,United Kingdom
|
||||
Hydra Supplies,6,"Manufacturer,Retailer",True,Palm Street 9,Los Angeles,United States
|
||||
Bolts & Screws,8,"Wholesaler,Components Buyer",True,Rua Américo 1000,Campinas,Brazil
|
||||
National Parts & Supplies,18,"Manufacturer,Wholesaler",True,Guangdong Way 20,Shenzen,China
|
||||
|
@@ -0,0 +1,9 @@
|
||||
Name,Is a company,Related company,Address type,Customer,Supplier,Street,ZIP,City,State,Country
|
||||
Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,CA,United States
|
||||
Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,CA,United States
|
||||
House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,CA,United States
|
||||
Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,CA,United States
|
||||
Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,CA,United States
|
||||
Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,CA,United States
|
||||
International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,DC,United States
|
||||
Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,DC,United States
|
||||
|
@@ -0,0 +1,10 @@
|
||||
Order Date,Order Reference,Supplier,Destination,Pricelist,Order Lines / Product,Order Lines / Quantity
|
||||
2012-12-15,PO00008,ASUSTeK,Stock,Default Purchase Pricelist,ADPT,20
|
||||
,,,,,CARD,30
|
||||
,,,,,C-Case,40
|
||||
2012-12-15,PO00009,Axelor,Stock,Default Purchase Pricelist,CD,5
|
||||
,,,,,CPUa8,15
|
||||
2012-12-15,PO000010,China Export,Stock,Default Purchase Pricelist,HDD SH-1,10
|
||||
,,,,,HDD SH-2,20
|
||||
,,,,,LAP-CUS,35
|
||||
,,,,,LAP-E5,40
|
||||
|
@@ -0,0 +1,6 @@
|
||||
"Order Reference","Supplier","Destination","Pricelist","Order Lines / Product","Order Lines / Quantity"
|
||||
"PO000020","ASUSTeK","Stock","Default Purchase Pricelist","ADPT",20
|
||||
,,,,"CARD",30
|
||||
,,,,"C-Case",40
|
||||
"PO000021","Axelor","Stock","Default Purchase Pricelist","CD",5
|
||||
,,,,"CPUa8",15
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,175 @@
|
||||
/* global Immutable, React */
|
||||
(function () {
|
||||
// NOTE: used by memento.rst
|
||||
'use strict';
|
||||
|
||||
function highlight(primary, secondary) {
|
||||
return {
|
||||
className: React.addons.classSet({
|
||||
related: primary,
|
||||
secondary: secondary
|
||||
})
|
||||
};
|
||||
}
|
||||
var AccountsTable = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div(
|
||||
{ style: { marginTop: '0' } },
|
||||
React.DOM.div(// P&L
|
||||
highlight(this.props.current === 'p-l'),
|
||||
React.DOM.h4(null, "Profit & Loss"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(
|
||||
highlight(null, this.props.current === 'retained'),
|
||||
"Net Profit"),
|
||||
React.DOM.div(
|
||||
highlight(null, this.props.current === 'gross-profit'),
|
||||
React.DOM.h5(
|
||||
highlight(this.props.current === 'gross-profit'),
|
||||
"Gross Profit"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Revenue"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Revenue"
|
||||
),
|
||||
React.DOM.dt(null, "Less ", "Costs of Revenue"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Cost of Goods Sold"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.div(
|
||||
highlight(this.props.current === 'opex'),
|
||||
React.DOM.h5(null, "Operating Income or Loss"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(
|
||||
null,
|
||||
"Less ", "Operating Expenses"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"R&D", React.DOM.br(),
|
||||
"Sales, General & Administrative"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Plus ", "Other Income"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Foreign Exchange Gains", React.DOM.br(),
|
||||
"Asset write-downs"
|
||||
),
|
||||
React.DOM.dt(
|
||||
null,
|
||||
"Less ", "Other Expenses"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Interest on debt", React.DOM.br(),
|
||||
"Depreciation"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.div(//Balance Sheet
|
||||
highlight(this.props.current === 'balance'),
|
||||
React.DOM.h4(null, "Balance Sheet"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(null, "Net Assets"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(highlight(this.props.current === 'assets'), "Total Assets"),
|
||||
React.DOM.dl(
|
||||
highlight(null, this.props.current === 'assets'),
|
||||
React.DOM.dt(null, "Current Assets"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Cash & Bank Accounts", React.DOM.br(),
|
||||
"Accounts Receivable", React.DOM.br(),
|
||||
"Deferred Tax Assets"
|
||||
),
|
||||
React.DOM.dt(null, "Plus ", "Non-current Assets"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Land & buildings", React.DOM.br(),
|
||||
"Intangible Assets"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.dl(
|
||||
highlight(this.props.current === 'liabilities'),
|
||||
React.DOM.dt(null, "Less ", "Current Liabilities"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Accounts Payable", React.DOM.br(),
|
||||
"Deferred Revenue", React.DOM.br(),
|
||||
"Deferred Tax Liabilities"),
|
||||
React.DOM.dt(null, "Less ", "Non-current liabilities"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Long-term loans")
|
||||
)
|
||||
),
|
||||
React.DOM.div(
|
||||
highlight(null, this.props.current === 'equity'),
|
||||
React.DOM.h5(highlight(this.props.current === 'equity'), "Total Equity"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Equity"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Common Stock"
|
||||
),
|
||||
React.DOM.dt(
|
||||
highlight(this.props.current === 'retained'),
|
||||
"Plus ", "Retained Earnings"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var target = document.querySelector('.accounts-table');
|
||||
if (!target) { return; }
|
||||
function render(current) {
|
||||
React.render(
|
||||
React.createElement(AccountsTable, { current: current }),
|
||||
target);
|
||||
}
|
||||
|
||||
var list = document.querySelectorAll('.intro-list p');
|
||||
Array.prototype.forEach.call(list, function (node) {
|
||||
node.addEventListener('mouseover', function (e) {
|
||||
if (!e.currentTarget.contains(e.target)) { return; }
|
||||
|
||||
e.currentTarget.classList.add('secondary');
|
||||
render(e.currentTarget.className.split(/\s+/).reduce(function (acc, cls) {
|
||||
if (acc) { return acc; }
|
||||
var m = /^intro-(.*)$/.exec(cls);
|
||||
return m && m[1];
|
||||
}, null));
|
||||
});
|
||||
node.addEventListener('mouseout', function (e) {
|
||||
// mouseout always precedes mousenter even when going into a
|
||||
// child element. Since re-render should be pretty fast (just
|
||||
// setting or unsetting a pair of classes) don't try to avoid
|
||||
// any thrashing, things should be fast enough either way. If
|
||||
// they're not, batch operations on requestAnimationFrame
|
||||
// instead.
|
||||
e.currentTarget.classList.remove('secondary');
|
||||
render(null);
|
||||
});
|
||||
});
|
||||
|
||||
render(null);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,56 @@
|
||||
function findAncestor(element, name) {
|
||||
name = name.toUpperCase();
|
||||
while(element && element.nodeName.toUpperCase() !== name) {
|
||||
element = element.parentElement;
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
||||
function createAtom(val, options) {
|
||||
var watchers = {};
|
||||
var validator = options && options.validator || function () { return true; };
|
||||
|
||||
function transition(next) {
|
||||
if (!validator(next)) {
|
||||
var err = new Error(next + " failed validation");
|
||||
err.name = "AssertionError";
|
||||
throw err;
|
||||
}
|
||||
|
||||
var prev = val;
|
||||
val = next;
|
||||
|
||||
Object.keys(watchers).forEach(function (k) {
|
||||
watchers[k](k, atom, prev, next);
|
||||
});
|
||||
}
|
||||
|
||||
var atom = {
|
||||
addWatch: function (key, fn) {
|
||||
watchers[key] = fn;
|
||||
},
|
||||
|
||||
removeWatch: function (key) {
|
||||
delete watchers[key];
|
||||
},
|
||||
|
||||
swap: function (fn) {
|
||||
var args = [val].concat([].slice.call(arguments, 1));
|
||||
transition(fn.apply(null, args));
|
||||
},
|
||||
|
||||
reset: function (v) {
|
||||
transition(v);
|
||||
},
|
||||
|
||||
deref: function () {
|
||||
return val;
|
||||
},
|
||||
|
||||
toString: function () {
|
||||
return "Atom(" + JSON.stringify(val) + ")";
|
||||
}
|
||||
};
|
||||
|
||||
return atom;
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
// NOTE: used by memento.rst
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.chart-of-accounts'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = findAncestor(document.querySelector('.chart-of-accounts'), 'section');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.insertBefore(controls, chart.lastElementChild);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
|
||||
BUILDINGS: { code: 17200, label: "Buildings" },
|
||||
DEPRECIATION: { code: 17800, label: "Accumulated Depreciation" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
DEFERRED_REVENUE: { code: 22300, label: "Deferred Revenue" },
|
||||
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
SALES_SERVICES: { code: 42000, label: "Services" }
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
|
||||
DEPRECIATION: { code: 52500, label: "Other Operating Expenses" },
|
||||
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
refund = sale,
|
||||
refund_tax = refund * 0.09,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Company Incorporation (Initial Capital $1,000)",
|
||||
operations: [
|
||||
{ account: ASSETS.BANK.code, debit: constant(1000) },
|
||||
{ account: EQUITY.CAPITAL.code, credit: constant(1000) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice ($100 + 9% tax) & Shipping of the Goods",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Goods Shipment to Customer",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}, {
|
||||
id: 'refund',
|
||||
label: "Customer Refund",
|
||||
operations: [
|
||||
{ account: REVENUE.SALES.code, debit: constant(refund) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, debit: constant(refund_tax) },
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: constant(refund + refund_tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Payment",
|
||||
operations: [
|
||||
{
|
||||
account: ASSETS.BANK.code, debit: function (ops) {
|
||||
var refund_op = operations.find(function (op) {
|
||||
return op.get('id') === 'refund';
|
||||
});
|
||||
return ops.contains(refund_op.get('operations'))
|
||||
? total - (refund + refund_tax)
|
||||
: total;
|
||||
}
|
||||
},
|
||||
{
|
||||
account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: function (ops) {
|
||||
var refund_op = operations.find(function (op) {
|
||||
return op.get('id') === 'refund';
|
||||
});
|
||||
return ops.contains(refund_op.get('operations'))
|
||||
? total - (refund + refund_tax)
|
||||
: total;
|
||||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Received (Purchase Order: $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(cor) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (Invoice: $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(cor_tax) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(cor + cor_tax) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (Invoice: $52 but PO $50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(purchase - cor) },
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(purchase_tax) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(purchase + purchase_tax) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill Paid ($52 + 9% tax)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(purchase + purchase_tax) },
|
||||
{ account: ASSETS.BANK.code, credit: constant(purchase + purchase_tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Acquire a building (purchase contract)",
|
||||
operations: [
|
||||
{ account: ASSETS.BUILDINGS.code, debit: constant(3000) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(300) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(3300) }
|
||||
]
|
||||
}, {
|
||||
label: "Pay for building",
|
||||
operations: [
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(3300) },
|
||||
{ account: ASSETS.BANK.code, credit: constant(3300) }
|
||||
]
|
||||
}, {
|
||||
label: "Yearly Asset Depreciation (10% per year)",
|
||||
operations: [
|
||||
{ account: EXPENSES.DEPRECIATION.code, debit: constant(300) },
|
||||
{ account: ASSETS.DEPRECIATION.code, credit: constant(300) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice (3 years service contract, $300)",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total * 3) },
|
||||
{ account: LIABILITIES.DEFERRED_REVENUE.code, credit: constant(sale * 3) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax * 3) }
|
||||
]
|
||||
}, {
|
||||
label: "Revenue Recognition (each year, including first)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.DEFERRED_REVENUE.code, debit: constant(sale) },
|
||||
{ account: REVENUE.SALES_SERVICES.code, credit: constant(sale) },
|
||||
]
|
||||
}, {
|
||||
id: 'pay_taxes',
|
||||
label: "Pay Taxes Due",
|
||||
operations: [
|
||||
{
|
||||
account: LIABILITIES.TAXES_PAYABLE.code, debit: function (ops) {
|
||||
var this_ops = operations.find(function (op) {
|
||||
return op.get('id') === 'pay_taxes';
|
||||
}).get('operations');
|
||||
return ops.filter(function (_ops) {
|
||||
return _ops !== this_ops;
|
||||
}).flatten(true).filter(function (op) {
|
||||
return op.get('account') === LIABILITIES.TAXES_PAYABLE.code
|
||||
}).reduce(function (acc, op) {
|
||||
return acc + op.get('credit', zero)(ops) - op.get('debit', zero)(ops);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
account: ASSETS.BANK.code, credit: function (ops) {
|
||||
return operations.find(function (op) {
|
||||
return op.get('id') === 'pay_taxes';
|
||||
}).getIn(['operations', 0, 'debit'])(ops);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,276 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.valuation-chart-anglo-saxon'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.valuation-chart-anglo-saxon');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
|
||||
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
|
||||
MANUFACTURING_OVERHEAD: { code: 52000, label: "Manufacturing Overhead" },
|
||||
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Vendor Bill (PO $50, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Supplier Goods Reception (PO $50, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(50) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (PO $48, Invoice $50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(2) },
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(48) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Supplier Goods Reception (PO $48, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(48) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(48) },
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Shipping",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}, {
|
||||
label: "Production Order",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
{ account: EXPENSES.MANUFACTURING_OVERHEAD.code, debit: constant(2) },
|
||||
{ account: ASSETS.RAW_MATERIALS.code, credit: constant(52) }
|
||||
]
|
||||
}]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,265 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.valuation-chart-continental'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.valuation-chart-continental');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
PURCHASED_GOODS: { code: 51000, label: "Purchased Goods" },
|
||||
PURCHASED_SERVICES: { code: 52000, label: "Purchased Services" },
|
||||
INVENTORY_VARIATIONS: { code: 58000, label: "Inventory Variations" },
|
||||
OTHER_OPERATING_EXPENSES: { code: 59000, label: "Other Operating Expenses" },
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Vendor Invoice (PO €50, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Reception (PO €50, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(50) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Invoice (PO €48, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Reception (PO €48, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(48) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(48) },
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice (€100 + 9% tax)",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Shipping",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,297 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom, findAncestor */
|
||||
(function () {
|
||||
'use strict';
|
||||
// NOTE: memento.rst
|
||||
|
||||
var data = createAtom();
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { entry: next }),
|
||||
document.getElementById('entries-control'));
|
||||
React.render(
|
||||
React.createElement(FormatEntry, { entry: next }),
|
||||
document.querySelector('.journal-entries'));
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var entries_section = findAncestor(document.querySelector('.journal-entries'), 'section');
|
||||
if (!entries_section) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'entries-control');
|
||||
entries_section.insertBefore(controls, entries_section.lastElementChild);
|
||||
|
||||
data.reset(entries.first());
|
||||
});
|
||||
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var _this = this;
|
||||
return React.DOM.div(
|
||||
null,
|
||||
entries.map(function (entry, index) {
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: index,
|
||||
style: { display: 'block' },
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'radio',
|
||||
checked: Immutable.is(entry, this.props.entry),
|
||||
onChange: function (e) {
|
||||
data.reset(entry);
|
||||
}
|
||||
}),
|
||||
' ',
|
||||
entry.get('title')
|
||||
);
|
||||
}, this),
|
||||
this.props.entry && React.DOM.p(null, this.props.entry.get('help'))
|
||||
);
|
||||
}
|
||||
});
|
||||
var FormatEntry = React.createClass({
|
||||
render: function () {
|
||||
var entry = this.props.entry;
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm d-c-table' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th(null, "Debit"),
|
||||
React.DOM.th(null, "Credit")
|
||||
)
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.render_rows()
|
||||
)
|
||||
),
|
||||
React.createElement(Listing, {
|
||||
heading: "Explanation",
|
||||
items: entry && entry.get('explanation')
|
||||
}),
|
||||
React.createElement(Listing, {
|
||||
heading: "Configuration",
|
||||
items: entry && entry.get('configuration')
|
||||
})
|
||||
);
|
||||
},
|
||||
render_rows: function () {
|
||||
if (!this.props.entry) { return; }
|
||||
return this.props.entry.get('operations').map(this.render_row);
|
||||
},
|
||||
render_row: function (entry, index) {
|
||||
if (!entry) {
|
||||
return React.DOM.tr(
|
||||
{ key: 'spacer-' + index },
|
||||
React.DOM.td({ colSpan: 3 }, "\u00A0")
|
||||
);
|
||||
}
|
||||
return React.DOM.tr(
|
||||
{ key: index },
|
||||
React.DOM.td(null, entry.get('account')),
|
||||
React.DOM.td(null, entry.get('debit')),
|
||||
React.DOM.td(null, entry.get('credit'))
|
||||
);
|
||||
}
|
||||
});
|
||||
var Listing = React.createClass({
|
||||
render: function () {
|
||||
if (!this.props.items || this.props.items.isEmpty()) {
|
||||
return React.DOM.div();
|
||||
}
|
||||
var items = this.props.items, epilog = Immutable.List();
|
||||
var idx = items.indexOf(null);
|
||||
if (idx !== -1) {
|
||||
epilog = items.slice(idx + 1);
|
||||
items = items.take(idx);
|
||||
}
|
||||
return React.DOM.div(
|
||||
{ className: 'entries-listing' },
|
||||
React.DOM.h4(null, this.props.heading, ':'),
|
||||
React.DOM.ul(
|
||||
null,
|
||||
items.map(function (item, index) {
|
||||
return React.DOM.li({ key: index }, item);
|
||||
})
|
||||
),
|
||||
epilog.map(function (item, index) {
|
||||
return React.DOM.p({ key: index }, item);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var entries = Immutable.fromJS([
|
||||
{
|
||||
title: "Company Incorporation",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 1000 },
|
||||
{ account: 'Equity: Common Stock', credit: 1000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company receives $1,000 in cash",
|
||||
"Shares worth of $1,000 belong to the founders",
|
||||
null,
|
||||
"The initial capital can be cash, but could also be intellectual property, goodwill from a previous company, licences, know how, etc…",
|
||||
"Sometimes, capital is not released immediately, accounts for \"capital to be released\" may be necessary."
|
||||
],
|
||||
configuration: []
|
||||
}, {
|
||||
title: "Customer Invoice ($100 + 9% tax)",
|
||||
operations: [
|
||||
{ account: 'Revenue: Goods', credit: 100 },
|
||||
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 },
|
||||
{ account: 'Assets: Accounts Receivable', debit: 109 },
|
||||
{ account: 'Assets: Inventory', credit: 50 },
|
||||
{ account: 'Expenses: Cost of Goods Sold', debit: 50 }
|
||||
],
|
||||
explanation: [
|
||||
"Revenues increase by $100",
|
||||
"A tax to pay at the end of the month of $9",
|
||||
"The customer owes $109",
|
||||
"The inventory is decreased by $50 (shipping of the goods)",
|
||||
"The cost of goods sold decreases the gross profit by $50"
|
||||
],
|
||||
configuration: [
|
||||
"Revenue: defined on the product, or the product category if not on the product, field Income Account",
|
||||
"Defered Tax Liabilities: defined on the tax used on the invoice line",
|
||||
"Accounts Receivable: defined on the customer (property)",
|
||||
"Inventory: defined on the category of the related product (property)",
|
||||
"Expenses: defined on the product, or the category of product (property)",
|
||||
null,
|
||||
"The fiscal position used on the invoice may have a rule that replaces the Income Account or the tax defined on the product by another one."
|
||||
]
|
||||
}, {
|
||||
title: "Customer payment",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 109 },
|
||||
{ account: 'Assets: Accounts Receivable', credit: 109 }
|
||||
],
|
||||
explanation: [
|
||||
"The company receives $109 in cash",
|
||||
"The customer owes $109 less"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: defined on the journal used when registering the payment, fields Default Credit Account and Default Debit Account",
|
||||
"Accounts Receivable: defined on the customer (property)"
|
||||
]
|
||||
}, {
|
||||
title: "Supplier Bill (Purchase Order $50 but Invoice $52)",
|
||||
operations: [
|
||||
{ account: 'Assets: Uninvoiced Inventory', debit: 50 },
|
||||
{ account: 'Assets: Deferred Tax Assets', debit: 4.68 },
|
||||
{ account: 'Expenses: Price Difference', debit: 2 },
|
||||
{ account: 'Liabilities: Accounts Payable', credit: 56.68 }
|
||||
],
|
||||
explanation: [
|
||||
"A temporary account is used to note goods to receive",
|
||||
"The purchase order provides prices of goods, the actual invoice may include extra costs such as shipping",
|
||||
"The company still needs to pay the vendor (traded an asset against a liability)"
|
||||
],
|
||||
configuration: [
|
||||
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
|
||||
"Deferred Tax Assets: defined on the tax used on the purchase order line",
|
||||
"Accounts Payable: defined on the supplier related to the bill",
|
||||
null,
|
||||
"In this scenario, the purchase order was $50 but the company received an invoice for $52 as there were extra shipping costs"
|
||||
]
|
||||
}, {
|
||||
title: "Supplier Goods Received (Purchase Order: $50)",
|
||||
operations: [
|
||||
{ account: 'Assets: Inventory', debit: 50 },
|
||||
{ account: 'Assets: Uninvoiced Inventory', credit: 50 },
|
||||
],
|
||||
explanation: [
|
||||
"Inventory is increased by $50, the expected amount coming from the purchase order",
|
||||
"A temporary account is used for the counterpart and will be cleared when receiving the invoice"
|
||||
],
|
||||
configuration: [
|
||||
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
|
||||
"Inventory: defined on the product category, field: Stock Valuation"
|
||||
]
|
||||
}, {
|
||||
title: "Buy an asset ($300,000 - no tax)",
|
||||
operations: [
|
||||
{ account: 'Assets: Buildings', debit: 300000 },
|
||||
{ account: 'Liabilities: Accounts Payable', credit: 300000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company gets an asset worth of $300,000",
|
||||
"The company needs to pay $300,000 to the vendor (traded an asset against a liability)"
|
||||
],
|
||||
configuration: [
|
||||
"Buildings: Defined on the Asset category selected on the supplier bill line",
|
||||
"Accounts Payable: defined on the supplier related to the bill (property)"
|
||||
]
|
||||
}, {
|
||||
title: "Pay supplier invoice",
|
||||
operations: [
|
||||
{ account: 'Liabilities: Accounts Payable', debit: 300000 },
|
||||
{ account: 'Assets: Cash', credit: 300000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company owns $300,000 less to the supplier (liabilities are settled)",
|
||||
"The company's cash is reduced by $300,000 (reduction of asset)"
|
||||
],
|
||||
configuration: [
|
||||
"Accounts Payable: defined on the supplier you pay (property)",
|
||||
"Cash: defined on the journal related to the payment method"
|
||||
]
|
||||
}, {
|
||||
title: "Cash sale (Sales Receipt)",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 109 },
|
||||
{ account: 'Revenue: Goods', credit: 100 },
|
||||
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 }
|
||||
],
|
||||
explanation: [
|
||||
"Company's cash is increased by $109",
|
||||
"Revenues increase by $100",
|
||||
"A tax of $9 has to be paid"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: Payment method defined on the Sales Receipt",
|
||||
"Sales: Defined on the product used in the sales receipt, or the category of product if empty",
|
||||
"Deferred Tax Liabilities: Defined on the tax used in the sales receipt (coming from the product)"
|
||||
]
|
||||
}, {
|
||||
title: "Customer pays invoice, 5% early payment rebate",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 950 },
|
||||
{ account: 'Revenue: Sales Discount', debit: 50 },
|
||||
{ account: 'Assets: Accounts Receivable', credit: 1000 }
|
||||
],
|
||||
explanation: [
|
||||
"Company's cash is increased by $950",
|
||||
"Sales discounts lowering effective revenues by $50",
|
||||
"The customer owns $1000 less to the company"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: is defined on the journal related to the payment / bank statement",
|
||||
"Sales Discount: is selected during the payment matching process",
|
||||
"Accounts Receivable: is defined on the customer associated to the payment"
|
||||
]
|
||||
}, {
|
||||
title: "Fiscal year closing — positive earnings and 50% dividends",
|
||||
operations: [
|
||||
{ account: 'Net Profit', debit: 1000 },
|
||||
{ account: 'Equity: Retained Earnings', credit: 500 },
|
||||
{ account: 'Liabilities: Dividend Payable', credit: 500 }
|
||||
],
|
||||
explanation: [
|
||||
"The P&L is cleared (net profit)",
|
||||
"50% is transferred to retained earnings",
|
||||
"50% will be paid to shareholders as dividends"
|
||||
],
|
||||
configuration: [
|
||||
"This transaction is recorded by the advisor before closing the fiscal year, depending on how the company uses its net profit."
|
||||
]
|
||||
}
|
||||
]);
|
||||
}());
|
||||
@@ -0,0 +1,217 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
// NOTE: used for double_entry.rst file
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null)
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
' ',
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
var UNIT_PRICE = 100;
|
||||
function format_qty(val) {
|
||||
if (val == null) { return ''; }
|
||||
if (val < 0) { return val; }
|
||||
return '+' + String(val);
|
||||
}
|
||||
function format_value(val) {
|
||||
if (isNaN(val)) { return ''; }
|
||||
if (val < 0) { return '-$' + String(Math.abs(val)); }
|
||||
return '$' + String(val);
|
||||
}
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(null, "Location"),
|
||||
React.DOM.th({ className: 'text-right' }, "Quantity"),
|
||||
React.DOM.th({ className: 'text-right' }, "Value"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.locations().map(function (data) {
|
||||
var highlight = false;
|
||||
return React.DOM.tr(
|
||||
{ key: toKey(data.get('label')) },
|
||||
React.DOM.th(null, data.get('level') ? '\u2001' : '', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight
|
||||
})
|
||||
}, format_qty(data.get('qty'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight
|
||||
})
|
||||
}, format_value(data.get('qty') * UNIT_PRICE))
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
locations: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
// {location: total_qty}
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc.update(op.get('location'), function (qty) {
|
||||
return (qty || 0) + op.get('qty');
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return locations.valueSeq().flatMap(function (loc) {
|
||||
var sub_locations = loc.get('locations').valueSeq().map(function (subloc) {
|
||||
return subloc.set('level', 1).set('qty', totals.get(subloc));
|
||||
});
|
||||
|
||||
return Immutable.Seq.of(loc.set('level', 0)).concat(sub_locations);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-of-locations-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.getElementById('chart-of-locations'));
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.chart-of-locations');
|
||||
if (!chart) { return; }
|
||||
|
||||
chart.setAttribute('id', 'chart-of-locations');
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-of-locations-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
active: null,
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
var locations = Immutable.fromJS({
|
||||
warehouse: {
|
||||
label: "Warehouse",
|
||||
locations: {
|
||||
zone1: { label: "Zone 1" },
|
||||
zone2: { label: "Zone 2" }
|
||||
}
|
||||
},
|
||||
partners: {
|
||||
label: "Partner Locations",
|
||||
locations: {
|
||||
customers: { label: "Customers" },
|
||||
suppliers: { label: "Suppliers" }
|
||||
}
|
||||
},
|
||||
virtual: {
|
||||
label: "Virtual Locations",
|
||||
locations: {
|
||||
initial: { label: "Initial Inventory" },
|
||||
loss: { label: "Inventory Loss" },
|
||||
scrap: { label: "Scrapped" },
|
||||
manufacturing: { label: "Manufacturing" }
|
||||
}
|
||||
}
|
||||
}, function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Initial Inventory",
|
||||
operations: [
|
||||
{ location: locations.getIn(['virtual', 'locations', 'initial']), qty: -3 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +3 }
|
||||
]
|
||||
}, {
|
||||
label: "Reception",
|
||||
operations: [
|
||||
{ location: locations.getIn(['partners', 'locations', 'suppliers']), qty: -2 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +2 }
|
||||
]
|
||||
}, {
|
||||
label: "Delivery",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Return",
|
||||
operations: [
|
||||
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: -1 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "1 product broken in Zone 1",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['virtual', 'locations', 'scrap']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Inventory check of Zone 1",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['virtual', 'locations', 'loss']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Move from Zone 1 to Zone 2",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone2']), qty: +1 }
|
||||
]
|
||||
}]);
|
||||
})();
|
||||
@@ -0,0 +1,101 @@
|
||||
(function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
alternatives();
|
||||
highlight();
|
||||
checks_handling();
|
||||
});
|
||||
|
||||
function highlight() {
|
||||
// NOTE: used by double-entry.rst
|
||||
$('.highlighter-list').each(function () {
|
||||
var $this = $(this),
|
||||
$target = $($this.data('target'));
|
||||
$this.on('mouseout', 'li', function (e) {
|
||||
$(e.currentTarget).removeClass('secondary');
|
||||
$target.find('.related').removeClass('related');
|
||||
}).on('mouseover', 'li', function (e) {
|
||||
if (!e.currentTarget.contains(e.target)) { return; }
|
||||
|
||||
var $li = $(e.currentTarget);
|
||||
// console.log($li, $li.data('highlight'), $target.find($li.data('highlight')));
|
||||
$li.addClass('secondary');
|
||||
$target.find($li.data('highlight')).addClass('related');
|
||||
});
|
||||
});
|
||||
}
|
||||
/** alternatives display:
|
||||
* - prepend control for each <dt>
|
||||
* - radio input with link to following dd
|
||||
* - label is <dt> content
|
||||
* - hide all first-level dt and dd (CSS)
|
||||
* - on change
|
||||
* - hide all dds
|
||||
* - show dd corresponding to the selected radio
|
||||
* - automatically select first control on startup
|
||||
*/
|
||||
function alternatives() {
|
||||
// NOTE: used by double-entry.rst & valuation_methods pages
|
||||
$('dl.alternatives').each(function (index) {
|
||||
var $list = $(this),
|
||||
$contents = $list.children('dd');
|
||||
var $controls = $('<div class="alternatives-controls">').append(
|
||||
$list.children('dt').map(function () {
|
||||
var label = document.createElement('label'),
|
||||
input = document.createElement('input');
|
||||
input.setAttribute('type', 'radio');
|
||||
input.setAttribute('name', 'alternatives-' + index);
|
||||
|
||||
var sibling = this;
|
||||
while ((sibling = sibling.nextSibling) && sibling.nodeType !== 1);
|
||||
input.content = sibling;
|
||||
|
||||
label.appendChild(input);
|
||||
label.appendChild(document.createTextNode(' '));
|
||||
label.appendChild(document.createTextNode(this.textContent));
|
||||
|
||||
return label;
|
||||
}))
|
||||
.insertBefore($list)
|
||||
.on('change', 'input', function (e) {
|
||||
// change event triggers only on newly selected input, not
|
||||
// on the one being deselected
|
||||
$contents.css('display', '');
|
||||
var content = e.target.content;
|
||||
content && (content.style.display = 'block');
|
||||
})
|
||||
.find('input:first').click();
|
||||
});
|
||||
}
|
||||
function checks_handling() {
|
||||
// NOTE: used by memento.rst
|
||||
var $section = $('.checks-handling');
|
||||
if (!$section.length) { return; }
|
||||
|
||||
var $ul = $section.find('ul')
|
||||
.find('li').each(function () {
|
||||
var txt = this.textContent;
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.firstChild)
|
||||
}
|
||||
|
||||
$('<label style="display: block;">')
|
||||
.append('<input type="radio" name="checks-handling">')
|
||||
.append(' ')
|
||||
.append(txt)
|
||||
.appendTo(this);
|
||||
}).end()
|
||||
.on('change', 'input', update);
|
||||
update();
|
||||
function update() {
|
||||
var $inputs = $ul.find('input');
|
||||
var idx = 0;
|
||||
$inputs.each(function (index) {
|
||||
if (this.checked) {
|
||||
idx = index;
|
||||
}
|
||||
}).eq(idx).prop('checked', true);
|
||||
$ul.nextAll('div').hide().eq(idx).show();
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,72 @@
|
||||
(function () {
|
||||
// NOTE: memento.rst
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var $rec = $('#reconciliation .reconciliation-example');
|
||||
if (!$rec.length) { return; }
|
||||
|
||||
var state = 0;
|
||||
var operations = [
|
||||
function reconcile2() {
|
||||
$rec.addClass('reconcile2');
|
||||
return 1;
|
||||
},
|
||||
function reconcile1() {
|
||||
$rec.addClass('reconcile1');
|
||||
return 2;
|
||||
},
|
||||
function unreconcile() {
|
||||
$rec.removeClass('reconcile1 reconcile2');
|
||||
$1.add($2).removeClass('reconciled');
|
||||
setTimeout(update_btn, 0);
|
||||
return 0;
|
||||
}
|
||||
];
|
||||
var $buttons = $('<div class="buttons">').on('click', 'button', function () {
|
||||
this.disabled = true;
|
||||
state = operations[state]();
|
||||
}).appendTo($rec);
|
||||
|
||||
var $1 = $rec.find('td:contains("Invoice 1"), td:contains("Payment 1")')
|
||||
.parent()
|
||||
.addClass('invoice1');
|
||||
var $2 = $rec.find('td:contains("Invoice 2"), td:contains("Payment 2")')
|
||||
.parent()
|
||||
.addClass('invoice2');
|
||||
|
||||
// will be called multiple times (each tr + each td), only take trs in
|
||||
// account
|
||||
$rec.on('animationend webkitAnimationEnd MSAnimationEnd', function (e) {
|
||||
switch (e.originalEvent.target) {
|
||||
case $1[0]:
|
||||
$1.addClass('reconciled');
|
||||
break;
|
||||
case $2[0]:
|
||||
$2.addClass('reconciled');
|
||||
break;
|
||||
}
|
||||
update_btn();
|
||||
});
|
||||
update_btn();
|
||||
|
||||
function update_btn() {
|
||||
var $reconcile = $('<button class="btn btn-success" type="button">')
|
||||
.text("Next Reconcile")
|
||||
.appendTo($buttons.empty())
|
||||
switch (state) {
|
||||
case 0:
|
||||
$reconcile.text("Reconcile");
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
$reconcile.prop('disabled', true);
|
||||
$('<button class="btn btn-primary" type="button">')
|
||||
.text("Unreconcile")
|
||||
.appendTo($buttons);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown button state " + state);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,130 @@
|
||||
\usepackage{droid}
|
||||
%\usepackage[default]{lato}
|
||||
\usepackage{inconsolata}
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
|
||||
% fancy titlepage
|
||||
\usepackage{amsmath}
|
||||
\usepackage{tikz}
|
||||
\usepackage{epigraph}
|
||||
\renewcommand\epigraphflush{flushright}
|
||||
\renewcommand\epigraphsize{\normalsize}
|
||||
\setlength\epigraphwidth{0.7\textwidth}
|
||||
|
||||
\definecolor{titlepagecolor}{rgb}{.63,.28,.54}
|
||||
|
||||
\DeclareFixedFont{\titlefont}{T1}{ppl}{b}{it}{0.5in}
|
||||
|
||||
\newcommand\titlepagedecoration{%
|
||||
\begin{tikzpicture}[remember picture,overlay,shorten >= -10pt]
|
||||
|
||||
\coordinate (aux1) at ([yshift=-15pt]current page.north east);
|
||||
\coordinate (aux2) at ([yshift=-410pt]current page.north east);
|
||||
\coordinate (aux3) at ([xshift=-4.5cm]current page.north east);
|
||||
\coordinate (aux4) at ([yshift=-150pt]current page.north east);
|
||||
\coordinate (aux5) at ([yshift=-355pt]current page.north west);
|
||||
|
||||
\begin{scope}[titlepagecolor!40,line width=12pt,rounded corners=12pt, opacity=0.2]
|
||||
\draw
|
||||
(aux1) -- coordinate (a)
|
||||
++(225:5) --
|
||||
++(-45:5.1) coordinate (b);
|
||||
\draw[shorten <= -10pt]
|
||||
(aux3) --
|
||||
(a) --
|
||||
(aux1);
|
||||
\draw[opacity=0.6,titlepagecolor,shorten <= -10pt]
|
||||
(b) --
|
||||
++(225:2.2) --
|
||||
++(-45:2.2);
|
||||
\end{scope}
|
||||
\draw[titlepagecolor,line width=30pt,rounded corners=8pt,shorten <= -10pt]
|
||||
(aux5) --
|
||||
++(15,0);
|
||||
\draw[titlepagecolor,line width=8pt,rounded corners=8pt,shorten <= -10pt]
|
||||
(aux4) --
|
||||
++(225:0.8) --
|
||||
++(-45:0.8);
|
||||
\begin{scope}[titlepagecolor!70,line width=6pt,rounded corners=8pt]
|
||||
\draw[shorten <= -10pt]
|
||||
(aux2) --
|
||||
++(225:3) coordinate[pos=0.45] (c) --
|
||||
++(-45:3.1);
|
||||
\draw
|
||||
(aux2) --
|
||||
(c) --
|
||||
++(135:2.5) --
|
||||
++(45:2.5) --
|
||||
++(-45:2.5) coordinate[pos=0.3] (d);
|
||||
\draw
|
||||
(d) -- +(45:1);
|
||||
\end{scope}
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
|
||||
|
||||
% No automatic numbering, dual HTML/PDF output requires hardcoded ones
|
||||
\setcounter{secnumdepth}{0}
|
||||
|
||||
% Max latex toc depth
|
||||
\setcounter{tocdepth}{2}
|
||||
|
||||
\makeatletter
|
||||
|
||||
|
||||
\setlength{\headheight}{15.2pt}
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\fancyfoot[L]{{\py@HeaderFamily\thepage}}
|
||||
\fancyhead[R]{{\py@HeaderFamily \@title}}
|
||||
\renewcommand{\headrulewidth}{0.1pt}
|
||||
\renewcommand{\footrulewidth}{0.1pt}
|
||||
|
||||
\renewcommand{\maketitle}{%
|
||||
\vspace*{-2.5cm}
|
||||
\begin{flushright}
|
||||
\sphinxlogo
|
||||
\py@HeaderFamily
|
||||
{\huge \@title }\par
|
||||
\end{flushright}
|
||||
\@thanks
|
||||
\setcounter{footnote}{0}
|
||||
\let\thanks\relax\let\maketitle\relax
|
||||
%\gdef\@thanks{}\gdef\@author{}\gdef\@title{}
|
||||
}
|
||||
|
||||
|
||||
% For sphinx 1.8.3+, the command is \sphinxmaketitle and the
|
||||
% layout is slightly different on the titlepage
|
||||
\providecommand\sphinxmaketitle{}
|
||||
\renewcommand{\sphinxmaketitle}{%
|
||||
\vspace*{-2.5cm}
|
||||
\begin{flushright}
|
||||
\sphinxlogo
|
||||
\py@HeaderFamily
|
||||
{\hspace{-0.5cm} \huge \@title }\par
|
||||
\end{flushright}
|
||||
\@thanks
|
||||
\setcounter{footnote}{0}
|
||||
\let\thanks\relax\let\maketitle\relax
|
||||
}
|
||||
|
||||
\newcommand{\makeodootitleold}{%
|
||||
\begin{titlepage}
|
||||
\titlepagedecoration
|
||||
\begin{center}
|
||||
\vspace{3cm}
|
||||
\sphinxlogo
|
||||
\end{center}
|
||||
|
||||
\vspace{2cm}
|
||||
|
||||
{ \bf \color{white} \Large \@title }
|
||||
%\fdsfamily
|
||||
|
||||
\end{titlepage}
|
||||
\newpage
|
||||
}
|
||||
|
||||
\makeatother
|
||||
Reference in New Issue
Block a user